/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxUtils =
{
    /**
     * Class: mxUtils
     *
     * A singleton class that provides cross-browser helper methods.
     * This is a global functionality. To access the functions in this
     * class, use the global classname appended by the functionname.
     * You may have to load chrome://global/content/contentAreaUtils.js
     * to disable certain security restrictions in Mozilla for the <open>,
     * <save>, <saveAs> and <copy> function.
     *
     * For example, the following code displays an error message:
     *
     * (code)
     * mxUtils.error('Browser is not supported!', 200, false);
     * (end)
     *
     * Variable: errorResource
     *
     * Specifies the resource key for the title of the error window. If the
     * resource for this key does not exist then the value is used as
     * the title. Default is 'error'.
     */
    errorResource: (mxClient.language != 'none') ? 'error' : '',

    /**
     * Variable: closeResource
     *
     * Specifies the resource key for the label of the close button. If the
     * resource for this key does not exist then the value is used as
     * the label. Default is 'close'.
     */
    closeResource: (mxClient.language != 'none') ? 'close' : '',

    /**
     * Variable: errorImage
     *
     * Defines the image used for error dialogs.
     */
    errorImage: mxClient.imageBasePath + '/error.gif',

    /**
     * Function: removeCursors
     *
     * Removes the cursors from the style of the given DOM node and its
     * descendants.
     *
     * Parameters:
     *
     * element - DOM node to remove the cursor style from.
     */
    removeCursors: function (element) {
        if (element.style != null) {
            element.style.cursor = '';
        }

        var children = element.childNodes;

        if (children != null) {
            var childCount = children.length;

            for (var i = 0; i < childCount; i += 1) {
                mxUtils.removeCursors(children[i]);
            }
        }
    },

    /**
     * Function: getCurrentStyle
     *
     * Returns the current style of the specified element.
     *
     * Parameters:
     *
     * element - DOM node whose current style should be returned.
     */
    getCurrentStyle: function () {
        if (mxClient.IS_IE && (document.documentMode == null || document.documentMode < 9)) {
            return function (element) {
                return (element != null) ? element.currentStyle : null;
            };
        }
        else {
            return function (element) {
                return (element != null) ?
                    window.getComputedStyle(element, '') :
                    null;
            };
        }
    }(),

    /**
     * Function: parseCssNumber
     *
     * Parses the given CSS numeric value adding handling for the values thin,
     * medium and thick (2, 4 and 6).
     */
    parseCssNumber: function (value) {
        if (value == 'thin') {
            value = '2';
        }
        else if (value == 'medium') {
            value = '4';
        }
        else if (value == 'thick') {
            value = '6';
        }

        value = parseFloat(value);

        if (isNaN(value)) {
            value = 0;
        }

        return value;
    },

    /**
     * Function: setPrefixedStyle
     *
     * Adds the given style with the standard name and an optional vendor prefix for the current
     * browser.
     *
     * (code)
     * mxUtils.setPrefixedStyle(node.style, 'transformOrigin', '0% 0%');
     * (end)
     */
    setPrefixedStyle: function () {
        var prefix = null;

        if (mxClient.IS_OT) {
            prefix = 'O';
        }
        else if (mxClient.IS_SF || mxClient.IS_GC) {
            prefix = 'Webkit';
        }
        else if (mxClient.IS_MT) {
            prefix = 'Moz';
        }
        else if (mxClient.IS_IE && document.documentMode >= 9 && document.documentMode < 10) {
            prefix = 'ms';
        }

        return function (style, name, value) {
            style[name] = value;

            if (prefix != null && name.length > 0) {
                name = prefix + name.substring(0, 1).toUpperCase() + name.substring(1);
                style[name] = value;
            }
        };
    }(),

    /**
     * Function: hasScrollbars
     *
     * Returns true if the overflow CSS property of the given node is either
     * scroll or auto.
     *
     * Parameters:
     *
     * node - DOM node whose style should be checked for scrollbars.
     */
    hasScrollbars: function (node) {
        var style = mxUtils.getCurrentStyle(node);

        return style != null && (style.overflow == 'scroll' || style.overflow == 'auto');
    },

    /**
     * Function: bind
     *
     * Returns a wrapper function that locks the execution scope of the given
     * function to the specified scope. Inside funct, the "this" keyword
     * becomes a reference to that scope.
     */
    bind: function (scope, funct) {
        return function () {
            return funct.apply(scope, arguments);
        };
    },

    /**
     * Function: eval
     *
     * Evaluates the given expression using eval and returns the JavaScript
     * object that represents the expression result. Supports evaluation of
     * expressions that define functions and returns the function object for
     * these expressions.
     *
     * Parameters:
     *
     * expr - A string that represents a JavaScript expression.
     */
    eval: function (expr) {
        var result = null;

        if (expr.indexOf('function') >= 0) {
            try {
                eval('var _mxJavaScriptExpression=' + expr);
                result = _mxJavaScriptExpression;
                // TODO: Use delete here?
                _mxJavaScriptExpression = null;
            }
            catch (e) {
                mxLog.warn(e.message + ' while evaluating ' + expr);
            }
        }
        else {
            try {
                result = eval(expr);
            }
            catch (e) {
                mxLog.warn(e.message + ' while evaluating ' + expr);
            }
        }

        return result;
    },

    /**
     * Function: findNode
     *
     * Returns the first node where attr equals value.
     * This implementation does not use XPath.
     */
    findNode: function (node, attr, value) {
        if (node.nodeType == mxConstants.NODETYPE_ELEMENT) {
            var tmp = node.getAttribute(attr);

            if (tmp != null && tmp == value) {
                return node;
            }
        }

        node = node.firstChild;

        while (node != null) {
            var result = mxUtils.findNode(node, attr, value);

            if (result != null) {
                return result;
            }

            node = node.nextSibling;
        }

        return null;
    },

    /**
     * Function: getFunctionName
     *
     * Returns the name for the given function.
     *
     * Parameters:
     *
     * f - JavaScript object that represents a function.
     */
    getFunctionName: function (f) {
        var str = null;

        if (f != null) {
            if (f.name != null) {
                str = f.name;
            }
            else {
                str = mxUtils.trim(f.toString());

                if (/^function\s/.test(str)) {
                    str = mxUtils.ltrim(str.substring(9));
                    var idx2 = str.indexOf('(');

                    if (idx2 > 0) {
                        str = str.substring(0, idx2);
                    }
                }
            }
        }

        return str;
    },

    /**
     * Function: indexOf
     *
     * Returns the index of obj in array or -1 if the array does not contain
     * the given object.
     *
     * Parameters:
     *
     * array - Array to check for the given obj.
     * obj - Object to find in the given array.
     */
    indexOf: function (array, obj) {
        if (array != null && obj != null) {
            for (var i = 0; i < array.length; i++) {
                if (array[i] == obj) {
                    return i;
                }
            }
        }

        return -1;
    },

    /**
     * Function: forEach
     *
     * Calls the given function for each element of the given array and returns
     * the array.
     *
     * Parameters:
     *
     * array - Array that contains the elements.
     * fn - Function to be called for each object.
     */
    forEach: function (array, fn) {
        if (array != null && fn != null) {
            for (var i = 0; i < array.length; i++) {
                fn(array[i]);
            }
        }

        return array;
    },

    /**
     * Function: remove
     *
     * Removes all occurrences of the given object in the given array or
     * object. If there are multiple occurrences of the object, be they
     * associative or as an array entry, all occurrences are removed from
     * the array or deleted from the object. By removing the object from
     * the array, all elements following the removed element are shifted
     * by one step towards the beginning of the array.
     *
     * The length of arrays is not modified inside this function.
     *
     * Parameters:
     *
     * obj - Object to find in the given array.
     * array - Array to check for the given obj.
     */
    remove: function (obj, array) {
        var result = null;

        if (typeof (array) == 'object') {
            var index = mxUtils.indexOf(array, obj);

            while (index >= 0) {
                array.splice(index, 1);
                result = obj;
                index = mxUtils.indexOf(array, obj);
            }
        }

        for (var key in array) {
            if (array[key] == obj) {
                delete array[key];
                result = obj;
            }
        }

        return result;
    },

    /**
     * Function: isNode
     *
     * Returns true if the given value is an XML node with the node name
     * and if the optional attribute has the specified value.
     *
     * This implementation assumes that the given value is a DOM node if the
     * nodeType property is numeric, that is, if isNaN returns false for
     * value.nodeType.
     *
     * Parameters:
     *
     * value - Object that should be examined as a node.
     * nodeName - String that specifies the node name.
     * attributeName - Optional attribute name to check.
     * attributeValue - Optional attribute value to check.
     */
    isNode: function (value, nodeName, attributeName, attributeValue) {
        if (value != null && !isNaN(value.nodeType) && (nodeName == null ||
            value.nodeName.toLowerCase() == nodeName.toLowerCase())) {
            return attributeName == null ||
                value.getAttribute(attributeName) == attributeValue;
        }

        return false;
    },

    /**
     * Function: isAncestorNode
     *
     * Returns true if the given ancestor is an ancestor of the
     * given DOM node in the DOM. This also returns true if the
     * child is the ancestor.
     *
     * Parameters:
     *
     * ancestor - DOM node that represents the ancestor.
     * child - DOM node that represents the child.
     */
    isAncestorNode: function (ancestor, child) {
        var parent = child;

        while (parent != null) {
            if (parent == ancestor) {
                return true;
            }

            parent = parent.parentNode;
        }

        return false;
    },

    /**
     * Function: getChildNodes
     *
     * Returns an array of child nodes that are of the given node type.
     *
     * Parameters:
     *
     * node - Parent DOM node to return the children from.
     * nodeType - Optional node type to return. Default is
     * <mxConstants.NODETYPE_ELEMENT>.
     */
    getChildNodes: function (node, nodeType) {
        nodeType = nodeType || mxConstants.NODETYPE_ELEMENT;

        var children = [];
        var tmp = node.firstChild;

        while (tmp != null) {
            if (tmp.nodeType == nodeType) {
                children.push(tmp);
            }

            tmp = tmp.nextSibling;
        }

        return children;
    },

    /**
     * Function: importNode
     *
     * Cross browser implementation for document.importNode. Uses document.importNode
     * in all browsers but IE, where the node is cloned by creating a new node and
     * copying all attributes and children into it using importNode, recursively.
     *
     * Parameters:
     *
     * doc - Document to import the node into.
     * node - Node to be imported.
     * allChildren - If all children should be imported.
     */
    importNode: function (doc, node, allChildren) {
        if (mxClient.IS_IE && (document.documentMode == null || document.documentMode < 10)) {
            return mxUtils.importNodeImplementation(doc, node, allChildren);
        }
        else {
            return doc.importNode(node, allChildren);
        }
    },

    /**
     * Function: importNodeImplementation
     *
     * Full DOM API implementation for importNode without using importNode API call.
     *
     * Parameters:
     *
     * doc - Document to import the node into.
     * node - Node to be imported.
     * allChildren - If all children should be imported.
     */
    importNodeImplementation: function (doc, node, allChildren) {
        switch (node.nodeType) {
            case 1: /* element */
                {
                    var newNode = doc.createElement(node.nodeName);

                    if (node.attributes && node.attributes.length > 0) {
                        for (var i = 0; i < node.attributes.length; i++) {
                            newNode.setAttribute(node.attributes[i].nodeName,
                                node.getAttribute(node.attributes[i].nodeName));
                        }
                    }

                    if (allChildren && node.childNodes && node.childNodes.length > 0) {
                        for (var i = 0; i < node.childNodes.length; i++) {
                            newNode.appendChild(mxUtils.importNodeImplementation(doc, node.childNodes[i], allChildren));
                        }
                    }

                    return newNode;
                    break;
                }
            case 3: /* text */
            case 4: /* cdata-section */
            case 8: /* comment */
                {
                    return doc.createTextNode((node.nodeValue != null) ? node.nodeValue : node.value);
                    break;
                }
        };
    },

    /**
     * Function: createXmlDocument
     *
     * Returns a new, empty XML document.
     */
    createXmlDocument: function () {
        var doc = null;

        if (document.implementation && document.implementation.createDocument) {
            doc = document.implementation.createDocument('', '', null);
        }
        else if ("ActiveXObject" in window) {
            doc = mxUtils.createMsXmlDocument();
        }

        return doc;
    },

    /**
     * Function: createMsXmlDocument
     *
     * Returns a new, empty Microsoft.XMLDOM document using ActiveXObject.
     */
    createMsXmlDocument: function () {
        var doc = new ActiveXObject('Microsoft.XMLDOM');
        doc.async = false;

        // Workaround for parsing errors with SVG DTD
        doc.validateOnParse = false;
        doc.resolveExternals = false;

        return doc;
    },

    /**
     * Function: parseXml
     *
     * Parses the specified XML string into a new XML document and returns the
     * new document.
     *
     * Example:
     *
     * (code)
     * var doc = mxUtils.parseXml(
     *   '<mxGraphModel><root><MyDiagram id="0"><mxCell/></MyDiagram>'+
     *   '<MyLayer id="1"><mxCell parent="0" /></MyLayer><MyObject id="2">'+
     *   '<mxCell style="strokeColor=blue;fillColor=red" parent="1" vertex="1">'+
     *   '<mxGeometry x="10" y="10" width="80" height="30" as="geometry"/>'+
     *   '</mxCell></MyObject></root></mxGraphModel>');
     * (end)
     *
     * Parameters:
     *
     * xml - String that contains the XML data.
     */
    parseXml: function () {
        if (window.DOMParser) {
            return function (xml) {
                var parser = new DOMParser();

                return parser.parseFromString(xml, 'text/xml');
            };
        }
        else // IE<=9
        {
            return function (xml) {
                var doc = mxUtils.createMsXmlDocument();
                doc.loadXML(xml);

                return doc;
            };
        }
    }(),

    /**
     * Function: clearSelection
     *
     * Clears the current selection in the page.
     */
    clearSelection: function () {
        if (document.selection) {
            return function () {
                document.selection.empty();
            };
        }
        else if (window.getSelection) {
            return function () {
                if (window.getSelection().empty) {
                    window.getSelection().empty();
                }
                else if (window.getSelection().removeAllRanges) {
                    window.getSelection().removeAllRanges();
                }
            };
        }
        else {
            return function () { };
        }
    }(),

    /**
     * Function: removeWhitespace
     *
     * Removes the sibling text nodes for the given node that only consists
     * of tabs, newlines and spaces.
     *
     * Parameters:
     *
     * node - DOM node whose siblings should be removed.
     * before - Optional boolean that specifies the direction of the traversal.
     */
    removeWhitespace: function (node, before) {
        var tmp = (before) ? node.previousSibling : node.nextSibling;

        while (tmp != null && tmp.nodeType == mxConstants.NODETYPE_TEXT) {
            var next = (before) ? tmp.previousSibling : tmp.nextSibling;
            var text = mxUtils.getTextContent(tmp);

            if (mxUtils.trim(text).length == 0) {
                tmp.parentNode.removeChild(tmp);
            }

            tmp = next;
        }
    },

    /**
     * Function: htmlEntities
     *
     * Replaces characters (less than, greater than, newlines and quotes) with
     * their HTML entities in the given string and returns the result.
     *
     * Parameters:
     *
     * s - String that contains the characters to be converted.
     * newline - If newlines should be replaced. Default is true.
     */
    htmlEntities: function (s, newline) {
        s = String(s || '');

        s = s.replace(/&/g, '&amp;'); // 38 26
        s = s.replace(/"/g, '&quot;'); // 34 22
        s = s.replace(/\'/g, '&#39;'); // 39 27
        s = s.replace(/</g, '&lt;'); // 60 3C
        s = s.replace(/>/g, '&gt;'); // 62 3E

        if (newline == null || newline) {
            s = s.replace(/\n/g, '&#xa;');
        }

        return s;
    },

    /**
     * Function: isVml
     *
     * Returns true if the given node is in the VML namespace.
     *
     * Parameters:
     *
     * node - DOM node whose tag urn should be checked.
     */
    isVml: function (node) {
        return node != null && node.tagUrn == 'urn:schemas-microsoft-com:vml';
    },

    /**
     * Function: getXml
     *
     * Returns the XML content of the specified node. For Internet Explorer,
     * all \r\n\t[\t]* are removed from the XML string and the remaining \r\n
     * are replaced by \n. All \n are then replaced with linefeed, or &#xa; if
     * no linefeed is defined.
     *
     * Parameters:
     *
     * node - DOM node to return the XML for.
     * linefeed - Optional string that linefeeds are converted into. Default is
     * &#xa;
     */
    getXml: function (node, linefeed) {
        var xml = '';

        if (mxClient.IS_IE || mxClient.IS_IE11) {
            xml = mxUtils.getPrettyXml(node, '', '', '');
        }
        else if (window.XMLSerializer != null) {
            var xmlSerializer = new XMLSerializer();
            xml = xmlSerializer.serializeToString(node);
        }
        else if (node.xml != null) {
            xml = node.xml.replace(/\r\n\t[\t]*/g, '').
                replace(/>\r\n/g, '>').
                replace(/\r\n/g, '\n');
        }

        // Replaces linefeeds with HTML Entities.
        linefeed = linefeed || '&#xa;';
        xml = xml.replace(/\n/g, linefeed);

        return xml;
    },

    /**
     * Function: getPrettyXML
     *
     * Returns a pretty printed string that represents the XML tree for the
     * given node. This method should only be used to print XML for reading,
     * use <getXml> instead to obtain a string for processing.
     *
     * Parameters:
     *
     * node - DOM node to return the XML for.
     * tab - Optional string that specifies the indentation for one level.
     * Default is two spaces.
     * indent - Optional string that represents the current indentation.
     * Default is an empty string.
     * newline - Option string that represents a linefeed. Default is '\n'.
     */
    getPrettyXml: function (node, tab, indent, newline, ns) {
        var result = [];

        if (node != null) {
            tab = (tab != null) ? tab : '  ';
            indent = (indent != null) ? indent : '';
            newline = (newline != null) ? newline : '\n';

            if (node.namespaceURI != null && node.namespaceURI != ns) {
                ns = node.namespaceURI;

                if (node.getAttribute('xmlns') == null) {
                    node.setAttribute('xmlns', node.namespaceURI);
                }
            }

            if (node.nodeType == mxConstants.NODETYPE_DOCUMENT) {
                result.push(mxUtils.getPrettyXml(node.documentElement, tab, indent, newline, ns));
            }
            else if (node.nodeType == mxConstants.NODETYPE_DOCUMENT_FRAGMENT) {
                var tmp = node.firstChild;

                if (tmp != null) {
                    while (tmp != null) {
                        result.push(mxUtils.getPrettyXml(tmp, tab, indent, newline, ns));
                        tmp = tmp.nextSibling;
                    }
                }
            }
            else if (node.nodeType == mxConstants.NODETYPE_COMMENT) {
                var value = mxUtils.getTextContent(node);

                if (value.length > 0) {
                    result.push(indent + '<!--' + value + '-->' + newline);
                }
            }
            else if (node.nodeType == mxConstants.NODETYPE_TEXT) {
                var value = mxUtils.trim(mxUtils.getTextContent(node));

                if (value.length > 0) {
                    result.push(indent + mxUtils.htmlEntities(value, false) + newline);
                }
            }
            else if (node.nodeType == mxConstants.NODETYPE_CDATA) {
                var value = mxUtils.getTextContent(node);

                if (value.length > 0) {
                    result.push(indent + '<![CDATA[' + value + ']]' + newline);
                }
            }
            else {
                result.push(indent + '<' + node.nodeName);

                // Creates the string with the node attributes
                // and converts all HTML entities in the values
                var attrs = node.attributes;

                if (attrs != null) {
                    for (var i = 0; i < attrs.length; i++) {
                        var val = mxUtils.htmlEntities(attrs[i].value);
                        result.push(' ' + attrs[i].nodeName + '="' + val + '"');
                    }
                }

                // Recursively creates the XML string for each child
                // node and appends it here with an indentation
                var tmp = node.firstChild;

                if (tmp != null) {
                    result.push('>' + newline);

                    while (tmp != null) {
                        result.push(mxUtils.getPrettyXml(tmp, tab, indent + tab, newline, ns));
                        tmp = tmp.nextSibling;
                    }

                    result.push(indent + '</' + node.nodeName + '>' + newline);
                }
                else {
                    result.push(' />' + newline);
                }
            }
        }

        return result.join('');
    },

    /**
     * Function: extractTextWithWhitespace
     *
     * Returns the text content of the specified node.
     *
     * Parameters:
     *
     * elems - DOM nodes to return the text for.
     */
    extractTextWithWhitespace: function (elems) {
        // Known block elements for handling linefeeds (list is not complete)
        var blocks = ['BLOCKQUOTE', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'OL', 'P', 'PRE', 'TABLE', 'UL'];
        var ret = [];

        function doExtract(elts) {
            // Single break should be ignored
            if (elts.length == 1 && (elts[0].nodeName == 'BR' ||
                elts[0].innerHTML == '\n')) {
                return;
            }

            for (var i = 0; i < elts.length; i++) {
                var elem = elts[i];

                // DIV with a br or linefeed forces a linefeed
                if (elem.nodeName == 'BR' || elem.innerHTML == '\n' ||
                    ((elts.length == 1 || i == 0) && (elem.nodeName == 'DIV' &&
                        elem.innerHTML.toLowerCase() == '<br>'))) {
                    ret.push('\n');
                }
                else {
                    if (elem.nodeType === 3 || elem.nodeType === 4) {
                        if (elem.nodeValue.length > 0) {
                            ret.push(elem.nodeValue);
                        }
                    }
                    else if (elem.nodeType !== 8 && elem.childNodes.length > 0) {
                        doExtract(elem.childNodes);
                    }

                    if (i < elts.length - 1 && mxUtils.indexOf(blocks, elts[i + 1].nodeName) >= 0) {
                        ret.push('\n');
                    }
                }
            }
        };

        doExtract(elems);

        return ret.join('');
    },

    /**
     * Function: replaceTrailingNewlines
     *
     * Replaces each trailing newline with the given pattern.
     */
    replaceTrailingNewlines: function (str, pattern) {
        // LATER: Check is this can be done with a regular expression
        var postfix = '';

        while (str.length > 0 && str.charAt(str.length - 1) == '\n') {
            str = str.substring(0, str.length - 1);
            postfix += pattern;
        }

        return str + postfix;
    },

    /**
     * Function: getTextContent
     *
     * Returns the text content of the specified node.
     *
     * Parameters:
     *
     * node - DOM node to return the text content for.
     */
    getTextContent: function (node) {
        // Only IE10-
        if (mxClient.IS_IE && node.innerText !== undefined) {
            return node.innerText;
        }
        else {
            return (node != null) ? node[(node.textContent === undefined) ? 'text' : 'textContent'] : '';
        }
    },

    /**
     * Function: setTextContent
     *
     * Sets the text content of the specified node.
     *
     * Parameters:
     *
     * node - DOM node to set the text content for.
     * text - String that represents the text content.
     */
    setTextContent: function (node, text) {
        if (node.innerText !== undefined) {
            node.innerText = text;
        }
        else {
            node[(node.textContent === undefined) ? 'text' : 'textContent'] = text;
        }
    },

    /**
     * Function: getInnerHtml
     *
     * Returns the inner HTML for the given node as a string or an empty string
     * if no node was specified. The inner HTML is the text representing all
     * children of the node, but not the node itself.
     *
     * Parameters:
     *
     * node - DOM node to return the inner HTML for.
     */
    getInnerHtml: function () {
        if (mxClient.IS_IE) {
            return function (node) {
                if (node != null) {
                    return node.innerHTML;
                }

                return '';
            };
        }
        else {
            return function (node) {
                if (node != null) {
                    var serializer = new XMLSerializer();
                    return serializer.serializeToString(node);
                }

                return '';
            };
        }
    }(),

    /**
     * Function: getOuterHtml
     *
     * Returns the outer HTML for the given node as a string or an empty
     * string if no node was specified. The outer HTML is the text representing
     * all children of the node including the node itself.
     *
     * Parameters:
     *
     * node - DOM node to return the outer HTML for.
     */
    getOuterHtml: function () {
        if (mxClient.IS_IE) {
            return function (node) {
                if (node != null) {
                    if (node.outerHTML != null) {
                        return node.outerHTML;
                    }
                    else {
                        var tmp = [];
                        tmp.push('<' + node.nodeName);

                        var attrs = node.attributes;

                        if (attrs != null) {
                            for (var i = 0; i < attrs.length; i++) {
                                var value = attrs[i].value;

                                if (value != null && value.length > 0) {
                                    tmp.push(' ');
                                    tmp.push(attrs[i].nodeName);
                                    tmp.push('="');
                                    tmp.push(value);
                                    tmp.push('"');
                                }
                            }
                        }

                        if (node.innerHTML.length == 0) {
                            tmp.push('/>');
                        }
                        else {
                            tmp.push('>');
                            tmp.push(node.innerHTML);
                            tmp.push('</' + node.nodeName + '>');
                        }

                        return tmp.join('');
                    }
                }

                return '';
            };
        }
        else {
            return function (node) {
                if (node != null) {
                    var serializer = new XMLSerializer();
                    return serializer.serializeToString(node);
                }

                return '';
            };
        }
    }(),

    /**
     * Function: write
     *
     * Creates a text node for the given string and appends it to the given
     * parent. Returns the text node.
     *
     * Parameters:
     *
     * parent - DOM node to append the text node to.
     * text - String representing the text to be added.
     */
    write: function (parent, text) {
        var doc = parent.ownerDocument;
        var node = doc.createTextNode(text);

        if (parent != null) {
            parent.appendChild(node);
        }

        return node;
    },

    /**
     * Function: writeln
     *
     * Creates a text node for the given string and appends it to the given
     * parent with an additional linefeed. Returns the text node.
     *
     * Parameters:
     *
     * parent - DOM node to append the text node to.
     * text - String representing the text to be added.
     */
    writeln: function (parent, text) {
        var doc = parent.ownerDocument;
        var node = doc.createTextNode(text);

        if (parent != null) {
            parent.appendChild(node);
            parent.appendChild(document.createElement('br'));
        }

        return node;
    },

    /**
     * Function: br
     *
     * Appends a linebreak to the given parent and returns the linebreak.
     *
     * Parameters:
     *
     * parent - DOM node to append the linebreak to.
     */
    br: function (parent, count) {
        count = count || 1;
        var br = null;

        for (var i = 0; i < count; i++) {
            if (parent != null) {
                br = parent.ownerDocument.createElement('br');
                parent.appendChild(br);
            }
        }

        return br;
    },

    /**
     * Function: button
     *
     * Returns a new button with the given level and function as an onclick
     * event handler.
     *
     * (code)
     * document.body.appendChild(mxUtils.button('Test', function(evt)
     * {
     *   alert('Hello, World!');
     * }));
     * (end)
     *
     * Parameters:
     *
     * label - String that represents the label of the button.
     * funct - Function to be called if the button is pressed.
     * doc - Optional document to be used for creating the button. Default is the
     * current document.
     */
    button: function (label, funct, doc) {
        doc = (doc != null) ? doc : document;

        var button = doc.createElement('button');
        mxUtils.write(button, label);

        mxEvent.addListener(button, 'click', function (evt) {
            funct(evt);
        });

        return button;
    },

    /**
     * Function: para
     *
     * Appends a new paragraph with the given text to the specified parent and
     * returns the paragraph.
     *
     * Parameters:
     *
     * parent - DOM node to append the text node to.
     * text - String representing the text for the new paragraph.
     */
    para: function (parent, text) {
        var p = document.createElement('p');
        mxUtils.write(p, text);

        if (parent != null) {
            parent.appendChild(p);
        }

        return p;
    },

    /**
     * Function: addTransparentBackgroundFilter
     *
     * Adds a transparent background to the filter of the given node. This
     * background can be used in IE8 standards mode (native IE8 only) to pass
     * events through the node.
     */
    addTransparentBackgroundFilter: function (node) {
        node.style.filter += 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' +
            mxClient.imageBasePath + '/transparent.gif\', sizingMethod=\'scale\')';
    },

    /**
     * Function: linkAction
     *
     * Adds a hyperlink to the specified parent that invokes action on the
     * specified editor.
     *
     * Parameters:
     *
     * parent - DOM node to contain the new link.
     * text - String that is used as the link label.
     * editor - <mxEditor> that will execute the action.
     * action - String that defines the name of the action to be executed.
     * pad - Optional left-padding for the link. Default is 0.
     */
    linkAction: function (parent, text, editor, action, pad) {
        return mxUtils.link(parent, text, function () {
            editor.execute(action);
        }, pad);
    },

    /**
     * Function: linkInvoke
     *
     * Adds a hyperlink to the specified parent that invokes the specified
     * function on the editor passing along the specified argument. The
     * function name is the name of a function of the editor instance,
     * not an action name.
     *
     * Parameters:
     *
     * parent - DOM node to contain the new link.
     * text - String that is used as the link label.
     * editor - <mxEditor> instance to execute the function on.
     * functName - String that represents the name of the function.
     * arg - Object that represents the argument to the function.
     * pad - Optional left-padding for the link. Default is 0.
     */
    linkInvoke: function (parent, text, editor, functName, arg, pad) {
        return mxUtils.link(parent, text, function () {
            editor[functName](arg);
        }, pad);
    },

    /**
     * Function: link
     *
     * Adds a hyperlink to the specified parent and invokes the given function
     * when the link is clicked.
     *
     * Parameters:
     *
     * parent - DOM node to contain the new link.
     * text - String that is used as the link label.
     * funct - Function to execute when the link is clicked.
     * pad - Optional left-padding for the link. Default is 0.
     */
    link: function (parent, text, funct, pad) {
        var a = document.createElement('span');

        a.style.color = 'blue';
        a.style.textDecoration = 'underline';
        a.style.cursor = 'pointer';

        if (pad != null) {
            a.style.paddingLeft = pad + 'px';
        }

        mxEvent.addListener(a, 'click', funct);
        mxUtils.write(a, text);

        if (parent != null) {
            parent.appendChild(a);
        }

        return a;
    },

    /**
     * Function: getDocumentSize
     *
     * Returns the client size for the current document as an <mxRectangle>.
     */
    getDocumentSize: function () {
        var b = document.body;
        var d = document.documentElement;

        try {
            return new mxRectangle(0, 0, b.clientWidth || d.clientWidth, Math.max(b.clientHeight || 0, d.clientHeight));
        }
        catch (e) {
            return new mxRectangle();
        }
    },

    /**
     * Function: fit
     *
     * Makes sure the given node is inside the visible area of the window. This
     * is done by setting the left and top in the style.
     */
    fit: function (node) {
        var ds = mxUtils.getDocumentSize();
        var left = parseInt(node.offsetLeft);
        var width = parseInt(node.offsetWidth);

        var offset = mxUtils.getDocumentScrollOrigin(node.ownerDocument);
        var sl = offset.x;
        var st = offset.y;

        var b = document.body;
        var d = document.documentElement;
        var right = (sl) + ds.width;

        if (left + width > right) {
            node.style.left = Math.max(sl, right - width) + 'px';
        }

        var top = parseInt(node.offsetTop);
        var height = parseInt(node.offsetHeight);

        var bottom = st + ds.height;

        if (top + height > bottom) {
            node.style.top = Math.max(st, bottom - height) + 'px';
        }
    },

    /**
     * Function: load
     *
     * Loads the specified URL *synchronously* and returns the <mxXmlRequest>.
     * Throws an exception if the file cannot be loaded. See <mxUtils.get> for
     * an asynchronous implementation.
     *
     * Example:
     *
     * (code)
     * try
     * {
     *   var req = mxUtils.load(filename);
     *   var root = req.getDocumentElement();
     *   // Process XML DOM...
     * }
     * catch (ex)
     * {
     *   mxUtils.alert('Cannot load '+filename+': '+ex);
     * }
     * (end)
     *
     * Parameters:
     *
     * url - URL to get the data from.
     */
    load: function (url) {
        var req = new mxXmlRequest(url, null, 'GET', false);
        req.send();

        return req;
    },

    /**
     * Function: get
     *
     * Loads the specified URL *asynchronously* and invokes the given functions
     * depending on the request status. Returns the <mxXmlRequest> in use. Both
     * functions take the <mxXmlRequest> as the only parameter. See
     * <mxUtils.load> for a synchronous implementation.
     *
     * Example:
     *
     * (code)
     * mxUtils.get(url, function(req)
     * {
     *    var node = req.getDocumentElement();
     *    // Process XML DOM...
     * });
     * (end)
     *
     * So for example, to load a diagram into an existing graph model, the
     * following code is used.
     *
     * (code)
     * mxUtils.get(url, function(req)
     * {
     *   var node = req.getDocumentElement();
     *   var dec = new mxCodec(node.ownerDocument);
     *   dec.decode(node, graph.getModel());
     * });
     * (end)
     *
     * Parameters:
     *
     * url - URL to get the data from.
     * onload - Optional function to execute for a successful response.
     * onerror - Optional function to execute on error.
     * binary - Optional boolean parameter that specifies if the request is
     * binary.
     * timeout - Optional timeout in ms before calling ontimeout.
     * ontimeout - Optional function to execute on timeout.
     * headers - Optional with headers, eg. {'Authorization': 'token xyz'}
     */
    get: function (url, onload, onerror, binary, timeout, ontimeout, headers) {
        var req = new mxXmlRequest(url, null, 'GET');
        var setRequestHeaders = req.setRequestHeaders;

        if (headers) {
            req.setRequestHeaders = function (request, params) {
                setRequestHeaders.apply(this, arguments);

                for (var key in headers) {
                    request.setRequestHeader(key, headers[key]);
                }
            };
        }

        if (binary != null) {
            req.setBinary(binary);
        }

        req.send(onload, onerror, timeout, ontimeout);

        return req;
    },

    /**
     * Function: getAll
     *
     * Loads the URLs in the given array *asynchronously* and invokes the given function
     * if all requests returned with a valid 2xx status. The error handler is invoked
     * once on the first error or invalid response.
     *
     * Parameters:
     *
     * urls - Array of URLs to be loaded.
     * onload - Callback with array of <mxXmlRequests>.
     * onerror - Optional function to execute on error.
     */
    getAll: function (urls, onload, onerror) {
        var remain = urls.length;
        var result = [];
        var errors = 0;
        var err = function () {
            if (errors == 0 && onerror != null) {
                onerror();
            }

            errors++;
        };

        for (var i = 0; i < urls.length; i++) {
            (function (url, index) {
                mxUtils.get(url, function (req) {
                    var status = req.getStatus();

                    if (status < 200 || status > 299) {
                        err();
                    }
                    else {
                        result[index] = req;
                        remain--;

                        if (remain == 0) {
                            onload(result);
                        }
                    }
                }, err);
            })(urls[i], i);
        }

        if (remain == 0) {
            onload(result);
        }
    },

    /**
     * Function: post
     *
     * Posts the specified params to the given URL *asynchronously* and invokes
     * the given functions depending on the request status. Returns the
     * <mxXmlRequest> in use. Both functions take the <mxXmlRequest> as the
     * only parameter. Make sure to use encodeURIComponent for the parameter
     * values.
     *
     * Example:
     *
     * (code)
     * mxUtils.post(url, 'key=value', function(req)
     * {
     * 	mxUtils.alert('Ready: '+req.isReady()+' Status: '+req.getStatus());
     *  // Process req.getDocumentElement() using DOM API if OK...
     * });
     * (end)
     *
     * Parameters:
     *
     * url - URL to get the data from.
     * params - Parameters for the post request.
     * onload - Optional function to execute for a successful response.
     * onerror - Optional function to execute on error.
     */
    post: function (url, params, onload, onerror) {
        return new mxXmlRequest(url, params).send(onload, onerror);
    },

    /**
     * Function: submit
     *
     * Submits the given parameters to the specified URL using
     * <mxXmlRequest.simulate> and returns the <mxXmlRequest>.
     * Make sure to use encodeURIComponent for the parameter
     * values.
     *
     * Parameters:
     *
     * url - URL to get the data from.
     * params - Parameters for the form.
     * doc - Document to create the form in.
     * target - Target to send the form result to.
     */
    submit: function (url, params, doc, target) {
        return new mxXmlRequest(url, params).simulate(doc, target);
    },

    /**
     * Function: loadInto
     *
     * Loads the specified URL *asynchronously* into the specified document,
     * invoking onload after the document has been loaded. This implementation
     * does not use <mxXmlRequest>, but the document.load method.
     *
     * Parameters:
     *
     * url - URL to get the data from.
     * doc - The document to load the URL into.
     * onload - Function to execute when the URL has been loaded.
     */
    loadInto: function (url, doc, onload) {
        if (mxClient.IS_IE) {
            doc.onreadystatechange = function () {
                if (doc.readyState == 4) {
                    onload();
                }
            };
        }
        else {
            doc.addEventListener('load', onload, false);
        }

        doc.load(url);
    },

    /**
     * Function: getValue
     *
     * Returns the value for the given key in the given associative array or
     * the given default value if the value is null.
     *
     * Parameters:
     *
     * array - Associative array that contains the value for the key.
     * key - Key whose value should be returned.
     * defaultValue - Value to be returned if the value for the given
     * key is null.
     */
    getValue: function (array, key, defaultValue) {
        var value = (array != null) ? array[key] : null;

        if (value == null) {
            value = defaultValue;
        }

        return value;
    },

    /**
     * Function: getNumber
     *
     * Returns the numeric value for the given key in the given associative
     * array or the given default value (or 0) if the value is null. The value
     * is converted to a numeric value using the Number function.
     *
     * Parameters:
     *
     * array - Associative array that contains the value for the key.
     * key - Key whose value should be returned.
     * defaultValue - Value to be returned if the value for the given
     * key is null. Default is 0.
     */
    getNumber: function (array, key, defaultValue) {
        var value = (array != null) ? array[key] : null;

        if (value == null) {
            value = defaultValue || 0;
        }

        return Number(value);
    },

    /**
     * Function: getColor
     *
     * Returns the color value for the given key in the given associative
     * array or the given default value if the value is null. If the value
     * is <mxConstants.NONE> then null is returned.
     *
     * Parameters:
     *
     * array - Associative array that contains the value for the key.
     * key - Key whose value should be returned.
     * defaultValue - Value to be returned if the value for the given
     * key is null. Default is null.
     */
    getColor: function (array, key, defaultValue) {
        var value = (array != null) ? array[key] : null;

        if (value == null) {
            value = defaultValue;
        }
        else if (value == mxConstants.NONE) {
            value = null;
        }

        return value;
    },

    /**
     * Function: clone
     *
     * Recursively clones the specified object ignoring all fieldnames in the
     * given array of transient fields. <mxObjectIdentity.FIELD_NAME> is always
     * ignored by this function.
     *
     * Parameters:
     *
     * obj - Object to be cloned.
     * transients - Optional array of strings representing the fieldname to be
     * ignored.
     * shallow - Optional boolean argument to specify if a shallow clone should
     * be created, that is, one where all object references are not cloned or,
     * in other words, one where only atomic (strings, numbers) values are
     * cloned. Default is false.
     */
    clone: function (obj, transients, shallow) {
        shallow = (shallow != null) ? shallow : false;
        var clone = null;

        if (obj != null && typeof (obj.constructor) == 'function') {
            clone = new obj.constructor();

            for (var i in obj) {
                if (i != mxObjectIdentity.FIELD_NAME && (transients == null ||
                    mxUtils.indexOf(transients, i) < 0)) {
                    if (!shallow && typeof (obj[i]) == 'object') {
                        clone[i] = mxUtils.clone(obj[i]);
                    }
                    else {
                        clone[i] = obj[i];
                    }
                }
            }
        }

        return clone;
    },

    /**
     * Function: equalPoints
     *
     * Compares all mxPoints in the given lists.
     *
     * Parameters:
     *
     * a - Array of <mxPoints> to be compared.
     * b - Array of <mxPoints> to be compared.
     */
    equalPoints: function (a, b) {
        if ((a == null && b != null) || (a != null && b == null) ||
            (a != null && b != null && a.length != b.length)) {
            return false;
        }
        else if (a != null && b != null) {
            for (var i = 0; i < a.length; i++) {
                if ((a[i] != null && b[i] == null) ||
                    (a[i] == null && b[i] != null) ||
                    (a[i] != null && b[i] != null &&
                        (a[i].x != b[i].x || a[i].y != b[i].y))) {
                    return false;
                }
            }
        }

        return true;
    },

    /**
     * Function: equalEntries
     *
     * Returns true if all properties of the given objects are equal. Values
     * with NaN are equal to NaN and unequal to any other value.
     *
     * Parameters:
     *
     * a - First object to be compared.
     * b - Second object to be compared.
     */
    equalEntries: function (a, b) {
        // Counts keys in b to check if all values have been compared
        var count = 0;

        if ((a == null && b != null) || (a != null && b == null) ||
            (a != null && b != null && a.length != b.length)) {
            return false;
        }
        else if (a != null && b != null) {
            for (var key in b) {
                count++;
            }

            for (var key in a) {
                count--

                if ((!mxUtils.isNaN(a[key]) || !mxUtils.isNaN(b[key])) && a[key] != b[key]) {
                    return false;
                }
            }
        }

        return count == 0;
    },

    /**
     * Function: removeDuplicates
     *
     * Removes all duplicates from the given array.
     */
    removeDuplicates: function (arr) {
        var dict = new mxDictionary();
        var result = [];

        for (var i = 0; i < arr.length; i++) {
            if (!dict.get(arr[i])) {
                result.push(arr[i]);
                dict.put(arr[i], true);
            }
        }

        return result;
    },

    /**
     * Function: isNaN
     *
     * Returns true if the given value is of type number and isNaN returns true.
     */
    isNaN: function (value) {
        return typeof (value) == 'number' && isNaN(value);
    },

    /**
     * Function: extend
     *
     * Assigns a copy of the superclass prototype to the subclass prototype.
     * Note that this does not call the constructor of the superclass at this
     * point, the superclass constructor should be called explicitely in the
     * subclass constructor. Below is an example.
     *
     * (code)
     * MyGraph = function(container, model, renderHint, stylesheet)
     * {
     *   mxGraph.call(this, container, model, renderHint, stylesheet);
     * }
     *
     * mxUtils.extend(MyGraph, mxGraph);
     * (end)
     *
     * Parameters:
     *
     * ctor - Constructor of the subclass.
     * superCtor - Constructor of the superclass.
     */
    extend: function (ctor, superCtor) {
        var f = function () { };
        f.prototype = superCtor.prototype;

        ctor.prototype = new f();
        ctor.prototype.constructor = ctor;
    },

    /**
     * Function: toString
     *
     * Returns a textual representation of the specified object.
     *
     * Parameters:
     *
     * obj - Object to return the string representation for.
     */
    toString: function (obj) {
        var output = '';

        for (var i in obj) {
            try {
                if (obj[i] == null) {
                    output += i + ' = [null]\n';
                }
                else if (typeof (obj[i]) == 'function') {
                    output += i + ' => [Function]\n';
                }
                else if (typeof (obj[i]) == 'object') {
                    var ctor = mxUtils.getFunctionName(obj[i].constructor);
                    output += i + ' => [' + ctor + ']\n';
                }
                else {
                    output += i + ' = ' + obj[i] + '\n';
                }
            }
            catch (e) {
                output += i + '=' + e.message;
            }
        }

        return output;
    },

    /**
     * Function: toRadians
     *
     * Converts the given degree to radians.
     */
    toRadians: function (deg) {
        return Math.PI * deg / 180;
    },

    /**
     * Function: toDegree
     *
     * Converts the given radians to degree.
     */
    toDegree: function (rad) {
        return rad * 180 / Math.PI;
    },

    /**
     * Function: arcToCurves
     *
     * Converts the given arc to a series of curves.
     */
    arcToCurves: function (x0, y0, r1, r2, angle, largeArcFlag, sweepFlag, x, y) {
        x -= x0;
        y -= y0;

        if (r1 === 0 || r2 === 0) {
            return result;
        }

        var fS = sweepFlag;
        var psai = angle;
        r1 = Math.abs(r1);
        r2 = Math.abs(r2);
        var ctx = -x / 2;
        var cty = -y / 2;
        var cpsi = Math.cos(psai * Math.PI / 180);
        var spsi = Math.sin(psai * Math.PI / 180);
        var rxd = cpsi * ctx + spsi * cty;
        var ryd = -1 * spsi * ctx + cpsi * cty;
        var rxdd = rxd * rxd;
        var rydd = ryd * ryd;
        var r1x = r1 * r1;
        var r2y = r2 * r2;
        var lamda = rxdd / r1x + rydd / r2y;
        var sds;

        if (lamda > 1) {
            r1 = Math.sqrt(lamda) * r1;
            r2 = Math.sqrt(lamda) * r2;
            sds = 0;
        }
        else {
            var seif = 1;

            if (largeArcFlag === fS) {
                seif = -1;
            }

            sds = seif * Math.sqrt((r1x * r2y - r1x * rydd - r2y * rxdd) / (r1x * rydd + r2y * rxdd));
        }

        var txd = sds * r1 * ryd / r2;
        var tyd = -1 * sds * r2 * rxd / r1;
        var tx = cpsi * txd - spsi * tyd + x / 2;
        var ty = spsi * txd + cpsi * tyd + y / 2;
        var rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - Math.atan2(0, 1);
        var s1 = (rad >= 0) ? rad : 2 * Math.PI + rad;
        rad = Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1) - Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1);
        var dr = (rad >= 0) ? rad : 2 * Math.PI + rad;

        if (fS == 0 && dr > 0) {
            dr -= 2 * Math.PI;
        }
        else if (fS != 0 && dr < 0) {
            dr += 2 * Math.PI;
        }

        var sse = dr * 2 / Math.PI;
        var seg = Math.ceil(sse < 0 ? -1 * sse : sse);
        var segr = dr / seg;
        var t = 8 / 3 * Math.sin(segr / 4) * Math.sin(segr / 4) / Math.sin(segr / 2);
        var cpsir1 = cpsi * r1;
        var cpsir2 = cpsi * r2;
        var spsir1 = spsi * r1;
        var spsir2 = spsi * r2;
        var mc = Math.cos(s1);
        var ms = Math.sin(s1);
        var x2 = -t * (cpsir1 * ms + spsir2 * mc);
        var y2 = -t * (spsir1 * ms - cpsir2 * mc);
        var x3 = 0;
        var y3 = 0;

        var result = [];

        for (var n = 0; n < seg; ++n) {
            s1 += segr;
            mc = Math.cos(s1);
            ms = Math.sin(s1);

            x3 = cpsir1 * mc - spsir2 * ms + tx;
            y3 = spsir1 * mc + cpsir2 * ms + ty;
            var dx = -t * (cpsir1 * ms + spsir2 * mc);
            var dy = -t * (spsir1 * ms - cpsir2 * mc);

            // CurveTo updates x0, y0 so need to restore it
            var index = n * 6;
            result[index] = Number(x2 + x0);
            result[index + 1] = Number(y2 + y0);
            result[index + 2] = Number(x3 - dx + x0);
            result[index + 3] = Number(y3 - dy + y0);
            result[index + 4] = Number(x3 + x0);
            result[index + 5] = Number(y3 + y0);

            x2 = x3 + dx;
            y2 = y3 + dy;
        }

        return result;
    },

    /**
     * Function: getBoundingBox
     *
     * Returns the bounding box for the rotated rectangle.
     *
     * Parameters:
     *
     * rect - <mxRectangle> to be rotated.
     * angle - Number that represents the angle (in degrees).
     * cx - Optional <mxPoint> that represents the rotation center. If no
     * rotation center is given then the center of rect is used.
     */
    getBoundingBox: function (rect, rotation, cx) {
        var result = null;

        if (rect != null && rotation != null && rotation != 0) {
            var rad = mxUtils.toRadians(rotation);
            var cos = Math.cos(rad);
            var sin = Math.sin(rad);

            cx = (cx != null) ? cx : new mxPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);

            var p1 = new mxPoint(rect.x, rect.y);
            var p2 = new mxPoint(rect.x + rect.width, rect.y);
            var p3 = new mxPoint(p2.x, rect.y + rect.height);
            var p4 = new mxPoint(rect.x, p3.y);

            p1 = mxUtils.getRotatedPoint(p1, cos, sin, cx);
            p2 = mxUtils.getRotatedPoint(p2, cos, sin, cx);
            p3 = mxUtils.getRotatedPoint(p3, cos, sin, cx);
            p4 = mxUtils.getRotatedPoint(p4, cos, sin, cx);

            result = new mxRectangle(p1.x, p1.y, 0, 0);
            result.add(new mxRectangle(p2.x, p2.y, 0, 0));
            result.add(new mxRectangle(p3.x, p3.y, 0, 0));
            result.add(new mxRectangle(p4.x, p4.y, 0, 0));
        }

        return result;
    },

    /**
     * Function: getRotatedPoint
     *
     * Rotates the given point by the given cos and sin.
     */
    getRotatedPoint: function (pt, cos, sin, c) {
        c = (c != null) ? c : new mxPoint();
        var x = pt.x - c.x;
        var y = pt.y - c.y;

        var x1 = x * cos - y * sin;
        var y1 = y * cos + x * sin;

        return new mxPoint(x1 + c.x, y1 + c.y);
    },

    /**
     * Returns an integer mask of the port constraints of the given map
     * @param dict the style map to determine the port constraints for
     * @param defaultValue Default value to return if the key is undefined.
     * @return the mask of port constraint directions
     *
     * Parameters:
     *
     * terminal - <mxCelState> that represents the terminal.
     * edge - <mxCellState> that represents the edge.
     * source - Boolean that specifies if the terminal is the source terminal.
     * defaultValue - Default value to be returned.
     */
    getPortConstraints: function (terminal, edge, source, defaultValue) {
        var value = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT,
            mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_SOURCE_PORT_CONSTRAINT :
                mxConstants.STYLE_TARGET_PORT_CONSTRAINT, null));

        if (value == null) {
            return defaultValue;
        }
        else {
            var directions = value.toString();
            var returnValue = mxConstants.DIRECTION_MASK_NONE;
            var constraintRotationEnabled = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT_ROTATION, 0);
            var rotation = 0;

            if (constraintRotationEnabled == 1) {
                rotation = mxUtils.getValue(terminal.style, mxConstants.STYLE_ROTATION, 0);
            }

            var quad = 0;

            if (rotation > 45) {
                quad = 1;

                if (rotation >= 135) {
                    quad = 2;
                }
            }
            else if (rotation < -45) {
                quad = 3;

                if (rotation <= -135) {
                    quad = 2;
                }
            }

            if (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0) {
                switch (quad) {
                    case 0:
                        returnValue |= mxConstants.DIRECTION_MASK_NORTH;
                        break;
                    case 1:
                        returnValue |= mxConstants.DIRECTION_MASK_EAST;
                        break;
                    case 2:
                        returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
                        break;
                    case 3:
                        returnValue |= mxConstants.DIRECTION_MASK_WEST;
                        break;
                }
            }
            if (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0) {
                switch (quad) {
                    case 0:
                        returnValue |= mxConstants.DIRECTION_MASK_WEST;
                        break;
                    case 1:
                        returnValue |= mxConstants.DIRECTION_MASK_NORTH;
                        break;
                    case 2:
                        returnValue |= mxConstants.DIRECTION_MASK_EAST;
                        break;
                    case 3:
                        returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
                        break;
                }
            }
            if (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0) {
                switch (quad) {
                    case 0:
                        returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
                        break;
                    case 1:
                        returnValue |= mxConstants.DIRECTION_MASK_WEST;
                        break;
                    case 2:
                        returnValue |= mxConstants.DIRECTION_MASK_NORTH;
                        break;
                    case 3:
                        returnValue |= mxConstants.DIRECTION_MASK_EAST;
                        break;
                }
            }
            if (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0) {
                switch (quad) {
                    case 0:
                        returnValue |= mxConstants.DIRECTION_MASK_EAST;
                        break;
                    case 1:
                        returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
                        break;
                    case 2:
                        returnValue |= mxConstants.DIRECTION_MASK_WEST;
                        break;
                    case 3:
                        returnValue |= mxConstants.DIRECTION_MASK_NORTH;
                        break;
                }
            }

            return returnValue;
        }
    },

    /**
     * Function: reversePortConstraints
     *
     * Reverse the port constraint bitmask. For example, north | east
     * becomes south | west
     */
    reversePortConstraints: function (constraint) {
        var result = 0;

        result = (constraint & mxConstants.DIRECTION_MASK_WEST) << 3;
        result |= (constraint & mxConstants.DIRECTION_MASK_NORTH) << 1;
        result |= (constraint & mxConstants.DIRECTION_MASK_SOUTH) >> 1;
        result |= (constraint & mxConstants.DIRECTION_MASK_EAST) >> 3;

        return result;
    },

    /**
     * Function: findNearestSegment
     *
     * Finds the index of the nearest segment on the given cell state for
     * the specified coordinate pair.
     */
    findNearestSegment: function (state, x, y) {
        var index = -1;

        if (state.absolutePoints.length > 0) {
            var last = state.absolutePoints[0];
            var min = null;

            for (var i = 1; i < state.absolutePoints.length; i++) {
                var current = state.absolutePoints[i];
                var dist = mxUtils.ptSegDistSq(last.x, last.y,
                    current.x, current.y, x, y);

                if (min == null || dist < min) {
                    min = dist;
                    index = i - 1;
                }

                last = current;
            }
        }

        return index;
    },

    /**
     * Function: getDirectedBounds
     *
     * Adds the given margins to the given rectangle and rotates and flips the
     * rectangle according to the respective styles in style.
     */
    getDirectedBounds: function (rect, m, style, flipH, flipV) {
        var d = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
        flipH = (flipH != null) ? flipH : mxUtils.getValue(style, mxConstants.STYLE_FLIPH, false);
        flipV = (flipV != null) ? flipV : mxUtils.getValue(style, mxConstants.STYLE_FLIPV, false);

        m.x = Math.round(Math.max(0, Math.min(rect.width, m.x)));
        m.y = Math.round(Math.max(0, Math.min(rect.height, m.y)));
        m.width = Math.round(Math.max(0, Math.min(rect.width, m.width)));
        m.height = Math.round(Math.max(0, Math.min(rect.height, m.height)));

        if ((flipV && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||
            (flipH && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST))) {
            var tmp = m.x;
            m.x = m.width;
            m.width = tmp;
        }

        if ((flipH && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||
            (flipV && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST))) {
            var tmp = m.y;
            m.y = m.height;
            m.height = tmp;
        }

        var m2 = mxRectangle.fromRectangle(m);

        if (d == mxConstants.DIRECTION_SOUTH) {
            m2.y = m.x;
            m2.x = m.height;
            m2.width = m.y;
            m2.height = m.width;
        }
        else if (d == mxConstants.DIRECTION_WEST) {
            m2.y = m.height;
            m2.x = m.width;
            m2.width = m.x;
            m2.height = m.y;
        }
        else if (d == mxConstants.DIRECTION_NORTH) {
            m2.y = m.width;
            m2.x = m.y;
            m2.width = m.height;
            m2.height = m.x;
        }

        return new mxRectangle(rect.x + m2.x, rect.y + m2.y, rect.width - m2.width - m2.x, rect.height - m2.height - m2.y);
    },

    /**
     * Function: getPerimeterPoint
     *
     * Returns the intersection between the polygon defined by the array of
     * points and the line between center and point.
     */
    getPerimeterPoint: function (pts, center, point) {
        var min = null;

        for (var i = 0; i < pts.length - 1; i++) {
            var pt = mxUtils.intersection(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y,
                center.x, center.y, point.x, point.y);

            if (pt != null) {
                var dx = point.x - pt.x;
                var dy = point.y - pt.y;
                var ip = { p: pt, distSq: dy * dy + dx * dx };

                if (ip != null && (min == null || min.distSq > ip.distSq)) {
                    min = ip;
                }
            }
        }

        return (min != null) ? min.p : null;
    },

    /**
     * Function: rectangleIntersectsSegment
     *
     * Returns true if the given rectangle intersects the given segment.
     *
     * Parameters:
     *
     * bounds - <mxRectangle> that represents the rectangle.
     * p1 - <mxPoint> that represents the first point of the segment.
     * p2 - <mxPoint> that represents the second point of the segment.
     */
    rectangleIntersectsSegment: function (bounds, p1, p2) {
        var top = bounds.y;
        var left = bounds.x;
        var bottom = top + bounds.height;
        var right = left + bounds.width;

        // Find min and max X for the segment
        var minX = p1.x;
        var maxX = p2.x;

        if (p1.x > p2.x) {
            minX = p2.x;
            maxX = p1.x;
        }

        // Find the intersection of the segment's and rectangle's x-projections
        if (maxX > right) {
            maxX = right;
        }

        if (minX < left) {
            minX = left;
        }

        if (minX > maxX) // If their projections do not intersect return false
        {
            return false;
        }

        // Find corresponding min and max Y for min and max X we found before
        var minY = p1.y;
        var maxY = p2.y;
        var dx = p2.x - p1.x;

        if (Math.abs(dx) > 0.0000001) {
            var a = (p2.y - p1.y) / dx;
            var b = p1.y - a * p1.x;
            minY = a * minX + b;
            maxY = a * maxX + b;
        }

        if (minY > maxY) {
            var tmp = maxY;
            maxY = minY;
            minY = tmp;
        }

        // Find the intersection of the segment's and rectangle's y-projections
        if (maxY > bottom) {
            maxY = bottom;
        }

        if (minY < top) {
            minY = top;
        }

        if (minY > maxY) // If Y-projections do not intersect return false
        {
            return false;
        }

        return true;
    },

    /**
     * Function: contains
     *
     * Returns true if the specified point (x, y) is contained in the given rectangle.
     *
     * Parameters:
     *
     * bounds - <mxRectangle> that represents the area.
     * x - X-coordinate of the point.
     * y - Y-coordinate of the point.
     */
    contains: function (bounds, x, y) {
        return (bounds.x <= x && bounds.x + bounds.width >= x &&
            bounds.y <= y && bounds.y + bounds.height >= y);
    },

    /**
     * Function: intersects
     *
     * Returns true if the two rectangles intersect.
     *
     * Parameters:
     *
     * a - <mxRectangle> to be checked for intersection.
     * b - <mxRectangle> to be checked for intersection.
     */
    intersects: function (a, b) {
        var tw = a.width;
        var th = a.height;
        var rw = b.width;
        var rh = b.height;

        if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) {
            return false;
        }

        var tx = a.x;
        var ty = a.y;
        var rx = b.x;
        var ry = b.y;

        rw += rx;
        rh += ry;
        tw += tx;
        th += ty;

        return ((rw < rx || rw > tx) &&
            (rh < ry || rh > ty) &&
            (tw < tx || tw > rx) &&
            (th < ty || th > ry));
    },

    /**
     * Function: intersectsHotspot
     *
     * Returns true if the state and the hotspot intersect.
     *
     * Parameters:
     *
     * state - <mxCellState>
     * x - X-coordinate.
     * y - Y-coordinate.
     * hotspot - Optional size of the hostpot.
     * min - Optional min size of the hostpot.
     * max - Optional max size of the hostpot.
     */
    intersectsHotspot: function (state, x, y, hotspot, min, max) {
        hotspot = (hotspot != null) ? hotspot : 1;
        min = (min != null) ? min : 0;
        max = (max != null) ? max : 0;

        if (hotspot > 0) {
            var cx = state.getCenterX();
            var cy = state.getCenterY();
            var w = state.width;
            var h = state.height;

            var start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE) * state.view.scale;

            if (start > 0) {
                if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, true)) {
                    cy = state.y + start / 2;
                    h = start;
                }
                else {
                    cx = state.x + start / 2;
                    w = start;
                }
            }

            w = Math.max(min, w * hotspot);
            h = Math.max(min, h * hotspot);

            if (max > 0) {
                w = Math.min(w, max);
                h = Math.min(h, max);
            }

            var rect = new mxRectangle(cx - w / 2, cy - h / 2, w, h);
            var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);

            if (alpha != 0) {
                var cos = Math.cos(-alpha);
                var sin = Math.sin(-alpha);
                var cx = new mxPoint(state.getCenterX(), state.getCenterY());
                var pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx);
                x = pt.x;
                y = pt.y;
            }

            return mxUtils.contains(rect, x, y);
        }

        return true;
    },

    /**
     * Function: getOffset
     *
     * Returns the offset for the specified container as an <mxPoint>. The
     * offset is the distance from the top left corner of the container to the
     * top left corner of the document.
     *
     * Parameters:
     *
     * container - DOM node to return the offset for.
     * scollOffset - Optional boolean to add the scroll offset of the document.
     * Default is false.
     */
    getOffset: function (container, scrollOffset) {
        var offsetLeft = 0;
        var offsetTop = 0;

        // Ignores document scroll origin for fixed elements
        var fixed = false;
        var node = container;
        var b = document.body;
        var d = document.documentElement;

        while (node != null && node != b && node != d && !fixed) {
            var style = mxUtils.getCurrentStyle(node);

            if (style != null) {
                fixed = fixed || style.position == 'fixed';
            }

            node = node.parentNode;
        }

        if (!scrollOffset && !fixed) {
            var offset = mxUtils.getDocumentScrollOrigin(container.ownerDocument);
            offsetLeft += offset.x;
            offsetTop += offset.y;
        }

        var r = container.getBoundingClientRect();

        if (r != null) {
            offsetLeft += r.left;
            offsetTop += r.top;
        }

        return new mxPoint(offsetLeft, offsetTop);
    },

    /**
     * Function: getDocumentScrollOrigin
     *
     * Returns the scroll origin of the given document or the current document
     * if no document is given.
     */
    getDocumentScrollOrigin: function (doc) {
        if (mxClient.IS_QUIRKS) {
            return new mxPoint(doc.body.scrollLeft, doc.body.scrollTop);
        }
        else {
            var wnd = doc.defaultView || doc.parentWindow;

            var x = (wnd != null && window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
            var y = (wnd != null && window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;

            return new mxPoint(x, y);
        }
    },

    /**
     * Function: getScrollOrigin
     *
     * Returns the top, left corner of the viewrect as an <mxPoint>.
     *
     * Parameters:
     *
     * node - DOM node whose scroll origin should be returned.
     * includeAncestors - Whether the scroll origin of the ancestors should be
     * included. Default is false.
     * includeDocument - Whether the scroll origin of the document should be
     * included. Default is true.
     */
    getScrollOrigin: function (node, includeAncestors, includeDocument) {
        includeAncestors = (includeAncestors != null) ? includeAncestors : false;
        includeDocument = (includeDocument != null) ? includeDocument : true;

        var doc = (node != null) ? node.ownerDocument : document;
        var b = doc.body;
        var d = doc.documentElement;
        var result = new mxPoint();
        var fixed = false;

        while (node != null && node != b && node != d) {
            if (!isNaN(node.scrollLeft) && !isNaN(node.scrollTop)) {
                result.x += node.scrollLeft;
                result.y += node.scrollTop;
            }

            var style = mxUtils.getCurrentStyle(node);

            if (style != null) {
                fixed = fixed || style.position == 'fixed';
            }

            node = (includeAncestors) ? node.parentNode : null;
        }

        if (!fixed && includeDocument) {
            var origin = mxUtils.getDocumentScrollOrigin(doc);

            result.x += origin.x;
            result.y += origin.y;
        }

        return result;
    },

    /**
     * Function: convertPoint
     *
     * Converts the specified point (x, y) using the offset of the specified
     * container and returns a new <mxPoint> with the result.
     *
     * (code)
     * var pt = mxUtils.convertPoint(graph.container,
     *   mxEvent.getClientX(evt), mxEvent.getClientY(evt));
     * (end)
     *
     * Parameters:
     *
     * container - DOM node to use for the offset.
     * x - X-coordinate of the point to be converted.
     * y - Y-coordinate of the point to be converted.
     */
    convertPoint: function (container, x, y) {
        var origin = mxUtils.getScrollOrigin(container, false);
        var offset = mxUtils.getOffset(container);

        offset.x -= origin.x;
        offset.y -= origin.y;

        return new mxPoint(x - offset.x, y - offset.y);
    },

    /**
     * Function: ltrim
     *
     * Strips all whitespaces from the beginning of the string. Without the
     * second parameter, this will trim these characters:
     *
     * - " " (ASCII 32 (0x20)), an ordinary space
     * - "\t" (ASCII 9 (0x09)), a tab
     * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
     * - "\r" (ASCII 13 (0x0D)), a carriage return
     * - "\0" (ASCII 0 (0x00)), the NUL-byte
     * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
     */
    ltrim: function (str, chars) {
        chars = chars || "\\s";

        return (str != null) ? str.replace(new RegExp("^[" + chars + "]+", "g"), "") : null;
    },

    /**
     * Function: rtrim
     *
     * Strips all whitespaces from the end of the string. Without the second
     * parameter, this will trim these characters:
     *
     * - " " (ASCII 32 (0x20)), an ordinary space
     * - "\t" (ASCII 9 (0x09)), a tab
     * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
     * - "\r" (ASCII 13 (0x0D)), a carriage return
     * - "\0" (ASCII 0 (0x00)), the NUL-byte
     * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
     */
    rtrim: function (str, chars) {
        chars = chars || "\\s";

        return (str != null) ? str.replace(new RegExp("[" + chars + "]+$", "g"), "") : null;
    },

    /**
     * Function: trim
     *
     * Strips all whitespaces from both end of the string.
     * Without the second parameter, Javascript function will trim these
     * characters:
     *
     * - " " (ASCII 32 (0x20)), an ordinary space
     * - "\t" (ASCII 9 (0x09)), a tab
     * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
     * - "\r" (ASCII 13 (0x0D)), a carriage return
     * - "\0" (ASCII 0 (0x00)), the NUL-byte
     * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
     */
    trim: function (str, chars) {
        return mxUtils.ltrim(mxUtils.rtrim(str, chars), chars);
    },

    /**
     * Function: isNumeric
     *
     * Returns true if the specified value is numeric, that is, if it is not
     * null, not an empty string, not a HEX number and isNaN returns false.
     *
     * Parameters:
     *
     * n - String representing the possibly numeric value.
     */
    isNumeric: function (n) {
        return !isNaN(parseFloat(n)) && isFinite(n) && (typeof (n) != 'string' || n.toLowerCase().indexOf('0x') < 0);
    },

    /**
     * Function: isInteger
     *
     * Returns true if the given value is an valid integer number.
     *
     * Parameters:
     *
     * n - String representing the possibly numeric value.
     */
    isInteger: function (n) {
        return String(parseInt(n)) === String(n);
    },

    /**
     * Function: mod
     *
     * Returns the remainder of division of n by m. You should use this instead
     * of the built-in operation as the built-in operation does not properly
     * handle negative numbers.
     */
    mod: function (n, m) {
        return ((n % m) + m) % m;
    },

    /**
     * Function: intersection
     *
     * Returns the intersection of two lines as an <mxPoint>.
     *
     * Parameters:
     *
     * x0 - X-coordinate of the first line's startpoint.
     * y0 - X-coordinate of the first line's startpoint.
     * x1 - X-coordinate of the first line's endpoint.
     * y1 - Y-coordinate of the first line's endpoint.
     * x2 - X-coordinate of the second line's startpoint.
     * y2 - Y-coordinate of the second line's startpoint.
     * x3 - X-coordinate of the second line's endpoint.
     * y3 - Y-coordinate of the second line's endpoint.
     */
    intersection: function (x0, y0, x1, y1, x2, y2, x3, y3) {
        var denom = ((y3 - y2) * (x1 - x0)) - ((x3 - x2) * (y1 - y0));
        var nume_a = ((x3 - x2) * (y0 - y2)) - ((y3 - y2) * (x0 - x2));
        var nume_b = ((x1 - x0) * (y0 - y2)) - ((y1 - y0) * (x0 - x2));

        var ua = nume_a / denom;
        var ub = nume_b / denom;

        if (ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0) {
            // Get the intersection point
            var x = x0 + ua * (x1 - x0);
            var y = y0 + ua * (y1 - y0);

            return new mxPoint(x, y);
        }

        // No intersection
        return null;
    },

    /**
     * Function: ptSegDistSq
     *
     * Returns the square distance between a segment and a point. To get the
     * distance between a point and a line (with infinite length) use
     * <mxUtils.ptLineDist>.
     *
     * Parameters:
     *
     * x1 - X-coordinate of the startpoint of the segment.
     * y1 - Y-coordinate of the startpoint of the segment.
     * x2 - X-coordinate of the endpoint of the segment.
     * y2 - Y-coordinate of the endpoint of the segment.
     * px - X-coordinate of the point.
     * py - Y-coordinate of the point.
     */
    ptSegDistSq: function (x1, y1, x2, y2, px, py) {
        x2 -= x1;
        y2 -= y1;

        px -= x1;
        py -= y1;

        var dotprod = px * x2 + py * y2;
        var projlenSq;

        if (dotprod <= 0.0) {
            projlenSq = 0.0;
        }
        else {
            px = x2 - px;
            py = y2 - py;
            dotprod = px * x2 + py * y2;

            if (dotprod <= 0.0) {
                projlenSq = 0.0;
            }
            else {
                projlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2);
            }
        }

        var lenSq = px * px + py * py - projlenSq;

        if (lenSq < 0) {
            lenSq = 0;
        }

        return lenSq;
    },

    /**
     * Function: ptLineDist
     *
     * Returns the distance between a line defined by two points and a point.
     * To get the distance between a point and a segment (with a specific
     * length) use <mxUtils.ptSeqDistSq>.
     *
     * Parameters:
     *
     * x1 - X-coordinate of point 1 of the line.
     * y1 - Y-coordinate of point 1 of the line.
     * x2 - X-coordinate of point 1 of the line.
     * y2 - Y-coordinate of point 1 of the line.
     * px - X-coordinate of the point.
     * py - Y-coordinate of the point.
     */
    ptLineDist: function (x1, y1, x2, y2, px, py) {
        return Math.abs((y2 - y1) * px - (x2 - x1) * py + x2 * y1 - y2 * x1) /
            Math.sqrt((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1));
    },

    /**
     * Function: relativeCcw
     *
     * Returns 1 if the given point on the right side of the segment, 0 if its
     * on the segment, and -1 if the point is on the left side of the segment.
     *
     * Parameters:
     *
     * x1 - X-coordinate of the startpoint of the segment.
     * y1 - Y-coordinate of the startpoint of the segment.
     * x2 - X-coordinate of the endpoint of the segment.
     * y2 - Y-coordinate of the endpoint of the segment.
     * px - X-coordinate of the point.
     * py - Y-coordinate of the point.
     */
    relativeCcw: function (x1, y1, x2, y2, px, py) {
        x2 -= x1;
        y2 -= y1;
        px -= x1;
        py -= y1;
        var ccw = px * y2 - py * x2;

        if (ccw == 0.0) {
            ccw = px * x2 + py * y2;

            if (ccw > 0.0) {
                px -= x2;
                py -= y2;
                ccw = px * x2 + py * y2;

                if (ccw < 0.0) {
                    ccw = 0.0;
                }
            }
        }

        return (ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0);
    },

    /**
     * Function: animateChanges
     *
     * See <mxEffects.animateChanges>. This is for backwards compatibility and
     * will be removed later.
     */
    animateChanges: function (graph, changes) {
        // LATER: Deprecated, remove this function
        mxEffects.animateChanges.apply(this, arguments);
    },

    /**
     * Function: cascadeOpacity
     *
     * See <mxEffects.cascadeOpacity>. This is for backwards compatibility and
     * will be removed later.
     */
    cascadeOpacity: function (graph, cell, opacity) {
        mxEffects.cascadeOpacity.apply(this, arguments);
    },

    /**
     * Function: fadeOut
     *
     * See <mxEffects.fadeOut>. This is for backwards compatibility and
     * will be removed later.
     */
    fadeOut: function (node, from, remove, step, delay, isEnabled) {
        mxEffects.fadeOut.apply(this, arguments);
    },

    /**
     * Function: setOpacity
     *
     * Sets the opacity of the specified DOM node to the given value in %.
     *
     * Parameters:
     *
     * node - DOM node to set the opacity for.
     * value - Opacity in %. Possible values are between 0 and 100.
     */
    setOpacity: function (node, value) {
        if (mxUtils.isVml(node)) {
            if (value >= 100) {
                node.style.filter = '';
            }
            else {
                // TODO: Why is the division by 5 needed in VML?
                node.style.filter = 'alpha(opacity=' + (value / 5) + ')';
            }
        }
        else if (mxClient.IS_IE && (typeof (document.documentMode) === 'undefined' || document.documentMode < 9)) {
            if (value >= 100) {
                node.style.filter = '';
            }
            else {
                node.style.filter = 'alpha(opacity=' + value + ')';
            }
        }
        else {
            node.style.opacity = (value / 100);
        }
    },

    /**
     * Function: createImage
     *
     * Creates and returns an image (IMG node) or VML image (v:image) in IE6 in
     * quirks mode.
     *
     * Parameters:
     *
     * src - URL that points to the image to be displayed.
     */
    createImage: function (src) {
        var imageNode = null;

        if (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat') {
            imageNode = document.createElement(mxClient.VML_PREFIX + ':image');
            imageNode.setAttribute('src', src);
            imageNode.style.borderStyle = 'none';
        }
        else {
            imageNode = document.createElement('img');
            imageNode.setAttribute('src', src);
            imageNode.setAttribute('border', '0');
        }

        return imageNode;
    },

    /**
     * Function: sortCells
     *
     * Sorts the given cells according to the order in the cell hierarchy.
     * Ascending is optional and defaults to true.
     */
    sortCells: function (cells, ascending) {
        ascending = (ascending != null) ? ascending : true;
        var lookup = new mxDictionary();
        cells.sort(function (o1, o2) {
            var p1 = lookup.get(o1);

            if (p1 == null) {
                p1 = mxCellPath.create(o1).split(mxCellPath.PATH_SEPARATOR);
                lookup.put(o1, p1);
            }

            var p2 = lookup.get(o2);

            if (p2 == null) {
                p2 = mxCellPath.create(o2).split(mxCellPath.PATH_SEPARATOR);
                lookup.put(o2, p2);
            }

            var comp = mxCellPath.compare(p1, p2);

            return (comp == 0) ? 0 : (((comp > 0) == ascending) ? 1 : -1);
        });

        return cells;
    },

    /**
     * Function: getStylename
     *
     * Returns the stylename in a style of the form [(stylename|key=value);] or
     * an empty string if the given style does not contain a stylename.
     *
     * Parameters:
     *
     * style - String of the form [(stylename|key=value);].
     */
    getStylename: function (style) {
        if (style != null) {
            var pairs = style.split(';');
            var stylename = pairs[0];

            if (stylename.indexOf('=') < 0) {
                return stylename;
            }
        }

        return '';
    },

    /**
     * Function: getStylenames
     *
     * Returns the stylenames in a style of the form [(stylename|key=value);]
     * or an empty array if the given style does not contain any stylenames.
     *
     * Parameters:
     *
     * style - String of the form [(stylename|key=value);].
     */
    getStylenames: function (style) {
        var result = [];

        if (style != null) {
            var pairs = style.split(';');

            for (var i = 0; i < pairs.length; i++) {
                if (pairs[i].indexOf('=') < 0) {
                    result.push(pairs[i]);
                }
            }
        }

        return result;
    },

    /**
     * Function: indexOfStylename
     *
     * Returns the index of the given stylename in the given style. This
     * returns -1 if the given stylename does not occur (as a stylename) in the
     * given style, otherwise it returns the index of the first character.
     */
    indexOfStylename: function (style, stylename) {
        if (style != null && stylename != null) {
            var tokens = style.split(';');
            var pos = 0;

            for (var i = 0; i < tokens.length; i++) {
                if (tokens[i] == stylename) {
                    return pos;
                }

                pos += tokens[i].length + 1;
            }
        }

        return -1;
    },

    /**
     * Function: addStylename
     *
     * Adds the specified stylename to the given style if it does not already
     * contain the stylename.
     */
    addStylename: function (style, stylename) {
        if (mxUtils.indexOfStylename(style, stylename) < 0) {
            if (style == null) {
                style = '';
            }
            else if (style.length > 0 && style.charAt(style.length - 1) != ';') {
                style += ';';
            }

            style += stylename;
        }

        return style;
    },

    /**
     * Function: removeStylename
     *
     * Removes all occurrences of the specified stylename in the given style
     * and returns the updated style. Trailing semicolons are not preserved.
     */
    removeStylename: function (style, stylename) {
        var result = [];

        if (style != null) {
            var tokens = style.split(';');

            for (var i = 0; i < tokens.length; i++) {
                if (tokens[i] != stylename) {
                    result.push(tokens[i]);
                }
            }
        }

        return result.join(';');
    },

    /**
     * Function: removeAllStylenames
     *
     * Removes all stylenames from the given style and returns the updated
     * style.
     */
    removeAllStylenames: function (style) {
        var result = [];

        if (style != null) {
            var tokens = style.split(';');

            for (var i = 0; i < tokens.length; i++) {
                // Keeps the key, value assignments
                if (tokens[i].indexOf('=') >= 0) {
                    result.push(tokens[i]);
                }
            }
        }

        return result.join(';');
    },

    /**
     * Function: setCellStyles
     *
     * Assigns the value for the given key in the styles of the given cells, or
     * removes the key from the styles if the value is null.
     *
     * Parameters:
     *
     * model - <mxGraphModel> to execute the transaction in.
     * cells - Array of <mxCells> to be updated.
     * key - Key of the style to be changed.
     * value - New value for the given key.
     */
    setCellStyles: function (model, cells, key, value) {
        if (cells != null && cells.length > 0) {
            model.beginUpdate();
            try {
                for (var i = 0; i < cells.length; i++) {
                    if (cells[i] != null) {
                        var style = mxUtils.setStyle(model.getStyle(cells[i]), key, value);
                        model.setStyle(cells[i], style);
                    }
                }
            }
            finally {
                model.endUpdate();
            }
        }
    },

    /**
     * Function: setStyle
     *
     * Adds or removes the given key, value pair to the style and returns the
     * new style. If value is null or zero length then the key is removed from
     * the style. This is for cell styles, not for CSS styles.
     *
     * Parameters:
     *
     * style - String of the form [(stylename|key=value);].
     * key - Key of the style to be changed.
     * value - New value for the given key.
     */
    setStyle: function (style, key, value) {
        var isValue = value != null && (typeof (value.length) == 'undefined' || value.length > 0);

        if (style == null || style.length == 0) {
            if (isValue) {
                style = key + '=' + value + ';';
            }
        }
        else {
            if (style.substring(0, key.length + 1) == key + '=') {
                var next = style.indexOf(';');

                if (isValue) {
                    style = key + '=' + value + ((next < 0) ? ';' : style.substring(next));
                }
                else {
                    style = (next < 0 || next == style.length - 1) ? '' : style.substring(next + 1);
                }
            }
            else {
                var index = style.indexOf(';' + key + '=');

                if (index < 0) {
                    if (isValue) {
                        var sep = (style.charAt(style.length - 1) == ';') ? '' : ';';
                        style = style + sep + key + '=' + value + ';';
                    }
                }
                else {
                    var next = style.indexOf(';', index + 1);

                    if (isValue) {
                        style = style.substring(0, index + 1) + key + '=' + value + ((next < 0) ? ';' : style.substring(next));
                    }
                    else {
                        style = style.substring(0, index) + ((next < 0) ? ';' : style.substring(next));
                    }
                }
            }
        }

        return style;
    },

    /**
     * Function: setCellStyleFlags
     *
     * Sets or toggles the flag bit for the given key in the cell's styles.
     * If value is null then the flag is toggled.
     *
     * Example:
     *
     * (code)
     * var cells = graph.getSelectionCells();
     * mxUtils.setCellStyleFlags(graph.model,
     * 			cells,
     * 			mxConstants.STYLE_FONTSTYLE,
     * 			mxConstants.FONT_BOLD);
     * (end)
     *
     * Toggles the bold font style.
     *
     * Parameters:
     *
     * model - <mxGraphModel> that contains the cells.
     * cells - Array of <mxCells> to change the style for.
     * key - Key of the style to be changed.
     * flag - Integer for the bit to be changed.
     * value - Optional boolean value for the flag.
     */
    setCellStyleFlags: function (model, cells, key, flag, value) {
        if (cells != null && cells.length > 0) {
            model.beginUpdate();
            try {
                for (var i = 0; i < cells.length; i++) {
                    if (cells[i] != null) {
                        var style = mxUtils.setStyleFlag(
                            model.getStyle(cells[i]),
                            key, flag, value);
                        model.setStyle(cells[i], style);
                    }
                }
            }
            finally {
                model.endUpdate();
            }
        }
    },

    /**
     * Function: setStyleFlag
     *
     * Sets or removes the given key from the specified style and returns the
     * new style. If value is null then the flag is toggled.
     *
     * Parameters:
     *
     * style - String of the form [(stylename|key=value);].
     * key - Key of the style to be changed.
     * flag - Integer for the bit to be changed.
     * value - Optional boolean value for the given flag.
     */
    setStyleFlag: function (style, key, flag, value) {
        if (style == null || style.length == 0) {
            if (value || value == null) {
                style = key + '=' + flag;
            }
            else {
                style = key + '=0';
            }
        }
        else {
            var index = style.indexOf(key + '=');

            if (index < 0) {
                var sep = (style.charAt(style.length - 1) == ';') ? '' : ';';

                if (value || value == null) {
                    style = style + sep + key + '=' + flag;
                }
                else {
                    style = style + sep + key + '=0';
                }
            }
            else {
                var cont = style.indexOf(';', index);
                var tmp = '';

                if (cont < 0) {
                    tmp = style.substring(index + key.length + 1);
                }
                else {
                    tmp = style.substring(index + key.length + 1, cont);
                }

                if (value == null) {
                    tmp = parseInt(tmp) ^ flag;
                }
                else if (value) {
                    tmp = parseInt(tmp) | flag;
                }
                else {
                    tmp = parseInt(tmp) & ~flag;
                }

                style = style.substring(0, index) + key + '=' + tmp +
                    ((cont >= 0) ? style.substring(cont) : '');
            }
        }

        return style;
    },

    /**
     * Function: getAlignmentAsPoint
     *
     * Returns an <mxPoint> that represents the horizontal and vertical alignment
     * for numeric computations. X is -0.5 for center, -1 for right and 0 for
     * left alignment. Y is -0.5 for middle, -1 for bottom and 0 for top
     * alignment. Default values for missing arguments is top, left.
     */
    getAlignmentAsPoint: function (align, valign) {
        var dx = -0.5;
        var dy = -0.5;

        // Horizontal alignment
        if (align == mxConstants.ALIGN_LEFT) {
            dx = 0;
        }
        else if (align == mxConstants.ALIGN_RIGHT) {
            dx = -1;
        }

        // Vertical alignment
        if (valign == mxConstants.ALIGN_TOP) {
            dy = 0;
        }
        else if (valign == mxConstants.ALIGN_BOTTOM) {
            dy = -1;
        }

        return new mxPoint(dx, dy);
    },

    /**
     * Function: getSizeForString
     *
     * Returns an <mxRectangle> with the size (width and height in pixels) of
     * the given string. The string may contain HTML markup. Newlines should be
     * converted to <br> before calling this method. The caller is responsible
     * for sanitizing the HTML markup.
     *
     * Example:
     *
     * (code)
     * var label = graph.getLabel(cell).replace(/\n/g, "<br>");
     * var size = graph.getSizeForString(label);
     * (end)
     *
     * Parameters:
     *
     * text - String whose size should be returned.
     * fontSize - Integer that specifies the font size in pixels. Default is
     * <mxConstants.DEFAULT_FONTSIZE>.
     * fontFamily - String that specifies the name of the font family. Default
     * is <mxConstants.DEFAULT_FONTFAMILY>.
     * textWidth - Optional width for text wrapping.
     * fontStyle - Optional font style.
     */
    getSizeForString: function (text, fontSize, fontFamily, textWidth, fontStyle) {
        fontSize = (fontSize != null) ? fontSize : mxConstants.DEFAULT_FONTSIZE;
        fontFamily = (fontFamily != null) ? fontFamily : mxConstants.DEFAULT_FONTFAMILY;
        var div = document.createElement('div');

        // Sets the font size and family
        div.style.fontFamily = fontFamily;
        div.style.fontSize = Math.round(fontSize) + 'px';
        div.style.lineHeight = Math.round(fontSize * mxConstants.LINE_HEIGHT) + 'px';

        // Sets the font style
        if (fontStyle != null) {
            if ((fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
                div.style.fontWeight = 'bold';
            }

            if ((fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
                div.style.fontStyle = 'italic';
            }

            var txtDecor = [];

            if ((fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
                txtDecor.push('underline');
            }

            if ((fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) {
                txtDecor.push('line-through');
            }

            if (txtDecor.length > 0) {
                div.style.textDecoration = txtDecor.join(' ');
            }
        }

        // Disables block layout and outside wrapping and hides the div
        div.style.position = 'absolute';
        div.style.visibility = 'hidden';
        div.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
        div.style.zoom = '1';

        if (textWidth != null) {
            div.style.width = textWidth + 'px';
            div.style.whiteSpace = 'normal';
        }
        else {
            div.style.whiteSpace = 'nowrap';
        }

        // Adds the text and inserts into DOM for updating of size
        div.innerHTML = text;
        document.body.appendChild(div);

        // Gets the size and removes from DOM
        var size = new mxRectangle(0, 0, div.offsetWidth, div.offsetHeight);
        document.body.removeChild(div);

        return size;
    },

    /**
     * Function: getViewXml
     */
    getViewXml: function (graph, scale, cells, x0, y0) {
        x0 = (x0 != null) ? x0 : 0;
        y0 = (y0 != null) ? y0 : 0;
        scale = (scale != null) ? scale : 1;

        if (cells == null) {
            var model = graph.getModel();
            cells = [model.getRoot()];
        }

        var view = graph.getView();
        var result = null;

        // Disables events on the view
        var eventsEnabled = view.isEventsEnabled();
        view.setEventsEnabled(false);

        // Workaround for label bounds not taken into account for image export.
        // Creates a temporary draw pane which is used for rendering the text.
        // Text rendering is required for finding the bounds of the labels.
        var drawPane = view.drawPane;
        var overlayPane = view.overlayPane;

        if (graph.dialect == mxConstants.DIALECT_SVG) {
            view.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
            view.canvas.appendChild(view.drawPane);

            // Redirects cell overlays into temporary container
            view.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
            view.canvas.appendChild(view.overlayPane);
        }
        else {
            view.drawPane = view.drawPane.cloneNode(false);
            view.canvas.appendChild(view.drawPane);

            // Redirects cell overlays into temporary container
            view.overlayPane = view.overlayPane.cloneNode(false);
            view.canvas.appendChild(view.overlayPane);
        }

        // Resets the translation
        var translate = view.getTranslate();
        view.translate = new mxPoint(x0, y0);

        // Creates the temporary cell states in the view
        var temp = new mxTemporaryCellStates(graph.getView(), scale, cells);

        try {
            var enc = new mxCodec();
            result = enc.encode(graph.getView());
        }
        finally {
            temp.destroy();
            view.translate = translate;
            view.canvas.removeChild(view.drawPane);
            view.canvas.removeChild(view.overlayPane);
            view.drawPane = drawPane;
            view.overlayPane = overlayPane;
            view.setEventsEnabled(eventsEnabled);
        }

        return result;
    },

    /**
     * Function: getScaleForPageCount
     *
     * Returns the scale to be used for printing the graph with the given
     * bounds across the specifies number of pages with the given format. The
     * scale is always computed such that it given the given amount or fewer
     * pages in the print output. See <mxPrintPreview> for an example.
     *
     * Parameters:
     *
     * pageCount - Specifies the number of pages in the print output.
     * graph - <mxGraph> that should be printed.
     * pageFormat - Optional <mxRectangle> that specifies the page format.
     * Default is <mxConstants.PAGE_FORMAT_A4_PORTRAIT>.
     * border - The border along each side of every page.
     */
    getScaleForPageCount: function (pageCount, graph, pageFormat, border) {
        if (pageCount < 1) {
            // We can't work with less than 1 page, return no scale
            // change
            return 1;
        }

        pageFormat = (pageFormat != null) ? pageFormat : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
        border = (border != null) ? border : 0;

        var availablePageWidth = pageFormat.width - (border * 2);
        var availablePageHeight = pageFormat.height - (border * 2);

        // Work out the number of pages required if the
        // graph is not scaled.
        var graphBounds = graph.getGraphBounds().clone();
        var sc = graph.getView().getScale();
        graphBounds.width /= sc;
        graphBounds.height /= sc;
        var graphWidth = graphBounds.width;
        var graphHeight = graphBounds.height;

        var scale = 1;

        // The ratio of the width/height for each printer page
        var pageFormatAspectRatio = availablePageWidth / availablePageHeight;
        // The ratio of the width/height for the graph to be printer
        var graphAspectRatio = graphWidth / graphHeight;

        // The ratio of horizontal pages / vertical pages for this
        // graph to maintain its aspect ratio on this page format
        var pagesAspectRatio = graphAspectRatio / pageFormatAspectRatio;

        // Factor the square root of the page count up and down
        // by the pages aspect ratio to obtain a horizontal and
        // vertical page count that adds up to the page count
        // and has the correct aspect ratio
        var pageRoot = Math.sqrt(pageCount);
        var pagesAspectRatioSqrt = Math.sqrt(pagesAspectRatio);
        var numRowPages = pageRoot * pagesAspectRatioSqrt;
        var numColumnPages = pageRoot / pagesAspectRatioSqrt;

        // These value are rarely more than 2 rounding downs away from
        // a total that meets the page count. In cases of one being less
        // than 1 page, the other value can be too high and take more iterations
        // In this case, just change that value to be the page count, since
        // we know the other value is 1
        if (numRowPages < 1 && numColumnPages > pageCount) {
            var scaleChange = numColumnPages / pageCount;
            numColumnPages = pageCount;
            numRowPages /= scaleChange;
        }

        if (numColumnPages < 1 && numRowPages > pageCount) {
            var scaleChange = numRowPages / pageCount;
            numRowPages = pageCount;
            numColumnPages /= scaleChange;
        }

        var currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);

        var numLoops = 0;

        // Iterate through while the rounded up number of pages comes to
        // a total greater than the required number
        while (currentTotalPages > pageCount) {
            // Round down the page count (rows or columns) that is
            // closest to its next integer down in percentage terms.
            // i.e. Reduce the page total by reducing the total
            // page area by the least possible amount

            var roundRowDownProportion = Math.floor(numRowPages) / numRowPages;
            var roundColumnDownProportion = Math.floor(numColumnPages) / numColumnPages;

            // If the round down proportion is, work out the proportion to
            // round down to 1 page less
            if (roundRowDownProportion == 1) {
                roundRowDownProportion = Math.floor(numRowPages - 1) / numRowPages;
            }
            if (roundColumnDownProportion == 1) {
                roundColumnDownProportion = Math.floor(numColumnPages - 1) / numColumnPages;
            }

            // Check which rounding down is smaller, but in the case of very small roundings
            // try the other dimension instead
            var scaleChange = 1;

            // Use the higher of the two values
            if (roundRowDownProportion > roundColumnDownProportion) {
                scaleChange = roundRowDownProportion;
            }
            else {
                scaleChange = roundColumnDownProportion;
            }

            numRowPages = numRowPages * scaleChange;
            numColumnPages = numColumnPages * scaleChange;
            currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);

            numLoops++;

            if (numLoops > 10) {
                break;
            }
        }

        // Work out the scale from the number of row pages required
        // The column pages will give the same value
        var posterWidth = availablePageWidth * numRowPages;
        scale = posterWidth / graphWidth;

        // Allow for rounding errors
        return scale * 0.99999;
    },

    /**
     * Function: show
     *
     * Copies the styles and the markup from the graph's container into the
     * given document and removes all cursor styles. The document is returned.
     *
     * This function should be called from within the document with the graph.
     * If you experience problems with missing stylesheets in IE then try adding
     * the domain to the trusted sites.
     *
     * Parameters:
     *
     * graph - <mxGraph> to be copied.
     * doc - Document where the new graph is created.
     * x0 - X-coordinate of the graph view origin. Default is 0.
     * y0 - Y-coordinate of the graph view origin. Default is 0.
     * w - Optional width of the graph view.
     * h - Optional height of the graph view.
     */
    show: function (graph, doc, x0, y0, w, h) {
        x0 = (x0 != null) ? x0 : 0;
        y0 = (y0 != null) ? y0 : 0;

        if (doc == null) {
            var wnd = window.open();
            doc = wnd.document;
        }
        else {
            doc.open();
        }

        // Workaround for missing print output in IE9 standards
        if (document.documentMode == 9) {
            doc.writeln('<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=9"><![endif]-->');
        }

        var bounds = graph.getGraphBounds();
        var dx = Math.ceil(x0 - bounds.x);
        var dy = Math.ceil(y0 - bounds.y);

        if (w == null) {
            w = Math.ceil(bounds.width + x0) + Math.ceil(Math.ceil(bounds.x) - bounds.x);
        }

        if (h == null) {
            h = Math.ceil(bounds.height + y0) + Math.ceil(Math.ceil(bounds.y) - bounds.y);
        }

        // Needs a special way of creating the page so that no click is required
        // to refresh the contents after the external CSS styles have been loaded.
        // To avoid a click or programmatic refresh, the styleSheets[].cssText
        // property is copied over from the original document.
        if (mxClient.IS_IE || document.documentMode == 11) {
            var html = '<html><head>';

            var base = document.getElementsByTagName('base');

            for (var i = 0; i < base.length; i++) {
                html += base[i].outerHTML;
            }

            html += '<style>';

            // Copies the stylesheets without having to load them again
            for (var i = 0; i < document.styleSheets.length; i++) {
                try {
                    html += document.styleSheets[i].cssText;
                }
                catch (e) {
                    // ignore security exception
                }
            }

            html += '</style></head><body style="margin:0px;">';

            // Copies the contents of the graph container
            html += '<div style="position:absolute;overflow:hidden;width:' + w + 'px;height:' + h + 'px;"><div style="position:relative;left:' + dx + 'px;top:' + dy + 'px;">';
            html += graph.container.innerHTML;
            html += '</div></div></body><html>';

            doc.writeln(html);
            doc.close();
        }
        else {
            doc.writeln('<html><head>');

            var base = document.getElementsByTagName('base');

            for (var i = 0; i < base.length; i++) {
                doc.writeln(mxUtils.getOuterHtml(base[i]));
            }

            var links = document.getElementsByTagName('link');

            for (var i = 0; i < links.length; i++) {
                doc.writeln(mxUtils.getOuterHtml(links[i]));
            }

            var styles = document.getElementsByTagName('style');

            for (var i = 0; i < styles.length; i++) {
                doc.writeln(mxUtils.getOuterHtml(styles[i]));
            }

            doc.writeln('</head><body style="margin:0px;"></body></html>');
            doc.close();

            var outer = doc.createElement('div');
            outer.position = 'absolute';
            outer.overflow = 'hidden';
            outer.style.width = w + 'px';
            outer.style.height = h + 'px';

            // Required for HTML labels if foreignObjects are disabled
            var div = doc.createElement('div');
            div.style.position = 'absolute';
            div.style.left = dx + 'px';
            div.style.top = dy + 'px';

            var node = graph.container.firstChild;
            var svg = null;

            while (node != null) {
                var clone = node.cloneNode(true);

                if (node == graph.view.drawPane.ownerSVGElement) {
                    outer.appendChild(clone);
                    svg = clone;
                }
                else {
                    div.appendChild(clone);
                }

                node = node.nextSibling;
            }

            doc.body.appendChild(outer);

            if (div.firstChild != null) {
                doc.body.appendChild(div);
            }

            if (svg != null) {
                svg.style.minWidth = '';
                svg.style.minHeight = '';
                svg.firstChild.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
            }
        }

        mxUtils.removeCursors(doc.body);

        return doc;
    },

    /**
     * Function: printScreen
     *
     * Prints the specified graph using a new window and the built-in print
     * dialog.
     *
     * This function should be called from within the document with the graph.
     *
     * Parameters:
     *
     * graph - <mxGraph> to be printed.
     */
    printScreen: function (graph) {
        var wnd = window.open();
        var bounds = graph.getGraphBounds();
        mxUtils.show(graph, wnd.document);

        var print = function () {
            wnd.focus();
            wnd.print();
            wnd.close();
        };

        // Workaround for Google Chrome which needs a bit of a
        // delay in order to render the SVG contents
        if (mxClient.IS_GC) {
            wnd.setTimeout(print, 500);
        }
        else {
            print();
        }
    },

    /**
     * Function: popup
     *
     * Shows the specified text content in a new <mxWindow> or a new browser
     * window if isInternalWindow is false.
     *
     * Parameters:
     *
     * content - String that specifies the text to be displayed.
     * isInternalWindow - Optional boolean indicating if an mxWindow should be
     * used instead of a new browser window. Default is false.
     */
    popup: function (content, isInternalWindow) {
        if (isInternalWindow) {
            var div = document.createElement('div');

            div.style.overflow = 'scroll';
            div.style.width = '636px';
            div.style.height = '460px';

            var pre = document.createElement('pre');
            pre.innerHTML = mxUtils.htmlEntities(content, false).
                replace(/\n/g, '<br>').replace(/ /g, '&nbsp;');

            div.appendChild(pre);

            var w = document.body.clientWidth;
            var h = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight)
            var wnd = new mxWindow('Popup Window', div,
                w / 2 - 320, h / 2 - 240, 640, 480, false, true);

            wnd.setClosable(true);
            wnd.setVisible(true);
        }
        else {
            // Wraps up the XML content in a textarea
            if (mxClient.IS_NS) {
                var wnd = window.open();
                wnd.document.writeln('<pre>' + mxUtils.htmlEntities(content) + '</pre');
                wnd.document.close();
            }
            else {
                var wnd = window.open();
                var pre = wnd.document.createElement('pre');
                pre.innerHTML = mxUtils.htmlEntities(content, false).
                    replace(/\n/g, '<br>').replace(/ /g, '&nbsp;');
                wnd.document.body.appendChild(pre);
            }
        }
    },

    /**
     * Function: alert
     *
     * Displayss the given alert in a new dialog. This implementation uses the
     * built-in alert function. This is used to display validation errors when
     * connections cannot be changed or created.
     *
     * Parameters:
     *
     * message - String specifying the message to be displayed.
     */
    alert: function (message) {
        alert(message);
    },

    /**
     * Function: prompt
     *
     * Displays the given message in a prompt dialog. This implementation uses
     * the built-in prompt function.
     *
     * Parameters:
     *
     * message - String specifying the message to be displayed.
     * defaultValue - Optional string specifying the default value.
     */
    prompt: function (message, defaultValue) {
        return prompt(message, (defaultValue != null) ? defaultValue : '');
    },

    /**
     * Function: confirm
     *
     * Displays the given message in a confirm dialog. This implementation uses
     * the built-in confirm function.
     *
     * Parameters:
     *
     * message - String specifying the message to be displayed.
     */
    confirm: function (message) {
        return confirm(message);
    },

    /**
     * Function: error
     *
     * Displays the given error message in a new <mxWindow> of the given width.
     * If close is true then an additional close button is added to the window.
     * The optional icon specifies the icon to be used for the window. Default
     * is <mxUtils.errorImage>.
     *
     * Parameters:
     *
     * message - String specifying the message to be displayed.
     * width - Integer specifying the width of the window.
     * close - Optional boolean indicating whether to add a close button.
     * icon - Optional icon for the window decoration.
     */
    error: function (message, width, close, icon) {
        var div = document.createElement('div');
        div.style.padding = '20px';

        var img = document.createElement('img');
        img.setAttribute('src', icon || mxUtils.errorImage);
        img.setAttribute('valign', 'bottom');
        img.style.verticalAlign = 'middle';
        div.appendChild(img);

        div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
        div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
        div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
        mxUtils.write(div, message);

        var w = document.body.clientWidth;
        var h = (document.body.clientHeight || document.documentElement.clientHeight);
        var warn = new mxWindow(mxResources.get(mxUtils.errorResource) ||
            mxUtils.errorResource, div, (w - width) / 2, h / 4, width, null,
            false, true);

        if (close) {
            mxUtils.br(div);

            var tmp = document.createElement('p');
            var button = document.createElement('button');

            if (mxClient.IS_IE) {
                button.style.cssText = 'float:right';
            }
            else {
                button.setAttribute('style', 'float:right');
            }

            mxEvent.addListener(button, 'click', function (evt) {
                warn.destroy();
            });

            mxUtils.write(button, mxResources.get(mxUtils.closeResource) ||
                mxUtils.closeResource);

            tmp.appendChild(button);
            div.appendChild(tmp);

            mxUtils.br(div);

            warn.setClosable(true);
        }

        warn.setVisible(true);

        return warn;
    },

    /**
     * Function: makeDraggable
     *
     * Configures the given DOM element to act as a drag source for the
     * specified graph. Returns a a new <mxDragSource>. If
     * <mxDragSource.guideEnabled> is enabled then the x and y arguments must
     * be used in funct to match the preview location.
     *
     * Example:
     *
     * (code)
     * var funct = function(graph, evt, cell, x, y)
     * {
     *   if (graph.canImportCell(cell))
     *   {
     *     var parent = graph.getDefaultParent();
     *     var vertex = null;
     *
     *     graph.getModel().beginUpdate();
     *     try
     *     {
     * 	     vertex = graph.insertVertex(parent, null, 'Hello', x, y, 80, 30);
     *     }
     *     finally
     *     {
     *       graph.getModel().endUpdate();
     *     }
     *
     *     graph.setSelectionCell(vertex);
     *   }
     * }
     *
     * var img = document.createElement('img');
     * img.setAttribute('src', 'editors/images/rectangle.gif');
     * img.style.position = 'absolute';
     * img.style.left = '0px';
     * img.style.top = '0px';
     * img.style.width = '16px';
     * img.style.height = '16px';
     *
     * var dragImage = img.cloneNode(true);
     * dragImage.style.width = '32px';
     * dragImage.style.height = '32px';
     * mxUtils.makeDraggable(img, graph, funct, dragImage);
     * document.body.appendChild(img);
     * (end)
     *
     * Parameters:
     *
     * element - DOM element to make draggable.
     * graphF - <mxGraph> that acts as the drop target or a function that takes a
     * mouse event and returns the current <mxGraph>.
     * funct - Function to execute on a successful drop.
     * dragElement - Optional DOM node to be used for the drag preview.
     * dx - Optional horizontal offset between the cursor and the drag
     * preview.
     * dy - Optional vertical offset between the cursor and the drag
     * preview.
     * autoscroll - Optional boolean that specifies if autoscroll should be
     * used. Default is mxGraph.autoscroll.
     * scalePreview - Optional boolean that specifies if the preview element
     * should be scaled according to the graph scale. If this is true, then
     * the offsets will also be scaled. Default is false.
     * highlightDropTargets - Optional boolean that specifies if dropTargets
     * should be highlighted. Default is true.
     * getDropTarget - Optional function to return the drop target for a given
     * location (x, y). Default is mxGraph.getCellAt.
     */
    makeDraggable: function (element, graphF, funct, dragElement, dx, dy, autoscroll,
        scalePreview, highlightDropTargets, getDropTarget) {
        var dragSource = new mxDragSource(element, funct);
        dragSource.dragOffset = new mxPoint((dx != null) ? dx : 0,
            (dy != null) ? dy : mxConstants.TOOLTIP_VERTICAL_OFFSET);
        dragSource.autoscroll = autoscroll;

        // Cannot enable this by default. This needs to be enabled in the caller
        // if the funct argument uses the new x- and y-arguments.
        dragSource.setGuidesEnabled(false);

        if (highlightDropTargets != null) {
            dragSource.highlightDropTargets = highlightDropTargets;
        }

        // Overrides function to find drop target cell
        if (getDropTarget != null) {
            dragSource.getDropTarget = getDropTarget;
        }

        // Overrides function to get current graph
        dragSource.getGraphForEvent = function (evt) {
            return (typeof (graphF) == 'function') ? graphF(evt) : graphF;
        };

        // Translates switches into dragSource customizations
        if (dragElement != null) {
            dragSource.createDragElement = function () {
                return dragElement.cloneNode(true);
            };

            if (scalePreview) {
                dragSource.createPreviewElement = function (graph) {
                    var elt = dragElement.cloneNode(true);

                    var w = parseInt(elt.style.width);
                    var h = parseInt(elt.style.height);
                    elt.style.width = Math.round(w * graph.view.scale) + 'px';
                    elt.style.height = Math.round(h * graph.view.scale) + 'px';

                    return elt;
                };
            }
        }

        return dragSource;
    }
};